Add reverse proxy SSL guides for Caddy, Nginx, and Traefik.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-29 03:55:37 +03:00
co-authored by Cursor
parent b1e31430b5
commit 93bbbf8ce8
12 changed files with 434 additions and 1 deletions
+28
View File
@@ -0,0 +1,28 @@
{$DOMAIN:panel.example.com} {
encode gzip zstd
# Healthcheck без редиректов наружу
handle /health {
reverse_proxy app:8080
}
reverse_proxy app:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up Host {host}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
log {
output stdout
format console
}
}
+38
View File
@@ -0,0 +1,38 @@
# Пример: Caddy как reverse proxy с авто-SSL (Let's Encrypt)
#
# Запуск вместе с панелью:
# docker compose -f docker-compose.yml -f deploy/caddy/docker-compose.caddy.yml up -d --build
#
# Перед стартом:
# 1. DNS A/AAAA домена → IP сервера
# 2. Открыты порты 80 и 443
# 3. В .env задайте DOMAIN=panel.example.com
# 4. В docker-compose панели лучше НЕ публиковать 8080 наружу (или оставить только localhost)
services:
caddy:
image: caddy:2-alpine
container_name: vpn-panel-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
DOMAIN: ${DOMAIN:-panel.example.com}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- vpn-net
depends_on:
- app
volumes:
caddy_data:
caddy_config:
networks:
vpn-net:
name: vpn-panel-net
+13
View File
@@ -0,0 +1,13 @@
# Override: не публиковать :8080 наружу (только через reverse proxy).
# Нужен Docker Compose v2.24+ (поддержка !override).
#
# docker compose -f docker-compose.yml -f deploy/compose.behind-proxy.yml up -d
#
# Если !override не поддерживается — в docker-compose.yml закомментируйте
# блок ports у сервиса app и оставьте только expose: ["8080"].
services:
app:
ports: !override []
expose:
- "8080"
+51
View File
@@ -0,0 +1,51 @@
# Пример: Nginx + Certbot (Let's Encrypt)
#
# Запуск:
# docker compose -f docker-compose.yml -f deploy/nginx/docker-compose.nginx.yml up -d --build
#
# Первый выпуск сертификата (после того как DNS уже указывает на сервер):
# docker compose -f deploy/nginx/docker-compose.nginx.yml run --rm certbot certonly \
# --webroot -w /var/www/certbot \
# -d panel.example.com \
# --email admin@example.com --agree-tos --no-eff-email
#
# Затем раскомментируйте HTTPS-server в nginx.conf и:
# docker compose -f docker-compose.yml -f deploy/nginx/docker-compose.nginx.yml up -d
#
# Обновление сертификатов (cron / systemd timer на хосте):
# docker compose -f deploy/nginx/docker-compose.nginx.yml run --rm certbot renew
# docker compose -f deploy/nginx/docker-compose.nginx.yml exec nginx nginx -s reload
services:
nginx:
image: nginx:1.27-alpine
container_name: vpn-panel-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./snippets:/etc/nginx/snippets:ro
- certbot_www:/var/www/certbot:ro
- certbot_certs:/etc/letsencrypt:ro
networks:
- vpn-net
depends_on:
- app
certbot:
image: certbot/certbot:latest
container_name: vpn-panel-certbot
volumes:
- certbot_www:/var/www/certbot
- certbot_certs:/etc/letsencrypt
entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done"
volumes:
certbot_www:
certbot_certs:
networks:
vpn-net:
name: vpn-panel-net
+42
View File
@@ -0,0 +1,42 @@
# Замените panel.example.com на свой DOMAIN.
# После выпуска сертификата Certbot раскомментируйте блок HTTPS ниже.
upstream vpn_panel {
server app:8080;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name panel.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Пока сертификата нет — проксируем HTTP.
# После выпуска SSL лучше редиректить на HTTPS (раскомментируйте return и закомментируйте location /).
# return 301 https://$host$request_uri;
location / {
proxy_pass http://vpn_panel;
include /etc/nginx/snippets/proxy_params.conf;
}
}
# --- HTTPS (раскомментировать после certbot certonly) ---
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name panel.example.com;
#
# ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
# include /etc/nginx/snippets/ssl_params.conf;
#
# location / {
# proxy_pass http://vpn_panel;
# include /etc/nginx/snippets/proxy_params.conf;
# }
# }
+10
View File
@@ -0,0 +1,10 @@
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Connection "";
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_buffering on;
+14
View File
@@ -0,0 +1,14 @@
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Modern Mozilla intermediate
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
+62
View File
@@ -0,0 +1,62 @@
# Пример: Traefik v3 + Let's Encrypt (HTTP challenge)
#
# Запуск:
# docker compose -f docker-compose.yml -f deploy/traefik/docker-compose.traefik.yml up -d --build
#
# В .env обязательны:
# DOMAIN=panel.example.com
# ACME_EMAIL=admin@example.com
#
# Если используете Dokploy — у него уже есть Traefik.
# Тогда НЕ поднимайте этот compose, а только повесьте labels на сервис app
# (см. labels.example.yml / README).
services:
traefik:
image: traefik:v3.3
container_name: vpn-panel-traefik
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=vpn-panel-net
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.le.acme.email=${ACME_EMAIL:-admin@example.com}
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --api.dashboard=false
- --log.level=INFO
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
networks:
- vpn-net
app:
labels:
- traefik.enable=true
- traefik.docker.network=vpn-panel-net
- traefik.http.routers.vpn-panel.rule=Host(`${DOMAIN:-panel.example.com}`)
- traefik.http.routers.vpn-panel.entrypoints=websecure
- traefik.http.routers.vpn-panel.tls=true
- traefik.http.routers.vpn-panel.tls.certresolver=le
- traefik.http.services.vpn-panel.loadbalancer.server.port=8080
- traefik.http.middlewares.vpn-panel-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.vpn-panel-headers.headers.stsIncludeSubdomains=true
- traefik.http.middlewares.vpn-panel-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.vpn-panel-headers.headers.frameDeny=true
- traefik.http.routers.vpn-panel.middlewares=vpn-panel-headers
volumes:
traefik_letsencrypt:
networks:
vpn-net:
name: vpn-panel-net
+22
View File
@@ -0,0 +1,22 @@
# Labels для сервиса app, если Traefik уже запущен (Dokploy / общий Traefik).
# Скопируйте в docker-compose.yml под services.app:
#
# labels:
# - traefik.enable=true
# - traefik.http.routers.vpn-panel.rule=Host(`panel.example.com`)
# - traefik.http.routers.vpn-panel.entrypoints=websecure
# - traefik.http.routers.vpn-panel.tls=true
# - traefik.http.routers.vpn-panel.tls.certresolver=letsencrypt
# - traefik.http.services.vpn-panel.loadbalancer.server.port=8080
#
# Имя certresolver в Dokploy часто: letsencrypt
services:
app:
labels:
- traefik.enable=true
- traefik.http.routers.vpn-panel.rule=Host(`${DOMAIN:-panel.example.com}`)
- traefik.http.routers.vpn-panel.entrypoints=websecure
- traefik.http.routers.vpn-panel.tls=true
- traefik.http.routers.vpn-panel.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}
- traefik.http.services.vpn-panel.loadbalancer.server.port=8080