Angie
Обзор
Angie — форк nginx с расширенным набором модулей, встроенным ACME-клиентом и поддержкой HTTP/3. Для панели XLADA он используется так же, как nginx: терминирует TLS и проксирует запросы на loopback-порты панели и страницы подписки.
Панель и страница подписки обязаны получать заголовки X-Forwarded-For и
X-Forwarded-Proto: https. В их коде есть middleware, который разрывает
соединение, если заголовков нет: без них панель не запустится за прокси. Реальный
IP клиента при этом нужен для HWID-ограничений, аудита действий администраторов и
защиты входа от перебора.
В примерах домены panel.example.com и sub.example.com, апстримы
127.0.0.1:3000 (панель) и 127.0.0.1:3010 (страница подписки) — это порты
контейнеров xpanel и xpanel-subpage на loopback хоста.
Нужен зарегистрированный домен. Сертификат Let's Encrypt выдать не получится, пока домен не указывает на ваш сервер.
DNS
Проверьте IP-адрес сервера (желательно статический) и создайте записи у DNS-провайдера или регистратора:
| Тип | Имя | Значение |
|---|---|---|
A | panel | IPv4-адрес сервера |
A | sub | IPv4-адрес сервера |
AAAA | panel | IPv6-адрес сервера |
AAAA | sub | IPv6-адрес сервера |
В Cloudflare записи создаются в разделе DNS → Records → Create record.
- Серое облако — трафик идёт напрямую, Angie видит реальный IP клиента; используйте этот режим, если Cloudflare недоступен или мешает.
- Оранжевое облако — трафик идёт через сеть Cloudflare: скрывается IP
сервера, работает защита от атак. В этом режиме Angie видит адреса Cloudflare,
поэтому для корректного IP включите модуль
realipи доверие к заголовкуCF-Connecting-IP.
Сертификаты TLS
Создайте каталоги для сертификатов и для ACME-проверок:
sudo mkdir -p /etc/angie/ssl /var/www/acme
Вариант 1: certbot
sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/acme \
-d panel.example.com -d sub.example.com \
--email admin@example.com --agree-tos --no-eff-email
sudo ln -sf /etc/letsencrypt/live/panel.example.com/fullchain.pem /etc/angie/ssl/fullchain.pem
sudo ln -sf /etc/letsencrypt/live/panel.example.com/privkey.pem /etc/angie/ssl/privkey.key
Вариант 2: acme.sh
curl https://get.acme.sh | sh -s email=admin@example.com
acme.sh --issue --webroot /var/www/acme \
-d panel.example.com -d sub.example.com \
--key-file /etc/angie/ssl/privkey.key \
--fullchain-file /etc/angie/ssl/fullchain.pem \
--reloadcmd "docker exec angie angie -s reload"
Оба клиента продлевают сертификат автоматически: certbot — своим systemd-таймером,
acme.sh — заданием в cron каждые 60 дней. Порт 80 должен быть открыт и отвечать
на путь ACME-проверки, который мы опишем в конфигурации ниже.
У Angie есть и собственный ACME-клиент: он выпускает сертификат без certbot и acme.sh. Полный конфиг для этого случая приведён в подразделе «Вариант со встроенным ACME».
Полная конфигурация angie.conf
sudo mkdir -p /opt/angie && cd /opt/angie
sudo nano angie.conf
# ─── Upstream-ы: контейнеры xpanel и xpanel-subpage ──────────────────
upstream xpanel_panel {
server 127.0.0.1:3000;
keepalive 32;
}
upstream xpanel_subpage {
server 127.0.0.1:3010;
keepalive 16;
}
# WebSocket: заголовок Connection подставляем только при Upgrade
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# ─── Gzip для всех виртуальных хостов ────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
application/wasm
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
# ─── HTTP: ACME-проверка и редирект на HTTPS ─────────────────────────
server {
listen 80;
listen [::]:80;
server_name panel.example.com sub.example.com;
location /.well-known/acme-challenge/ {
root /var/www/acme;
}
location / {
return 301 https://$host$request_uri;
}
}
# ─── Панель XLADA ────────────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;
# TLS (Mozilla Intermediate)
ssl_certificate /etc/angie/ssl/fullchain.pem;
ssl_certificate_key /etc/angie/ssl/privkey.key;
ssl_trusted_certificate /etc/angie/ssl/fullchain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# Размер тела запроса — с запасом для загрузки файлов через интерфейс
client_max_body_size 25m;
# Статика интерфейса: хеш в имени файла позволяет кэшировать её надолго
location ^~ /assets/ {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;
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 https;
proxy_set_header Connection "";
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;
# Панель рвёт соединение без этих двух заголовков
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 https;
proxy_set_header X-Forwarded-Host $host;
# WebSocket — SSH-терминал к нодам
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
proxy_buffering off; # стриминг и SSE
proxy_cache off;
}
}
# ─── Страница подписки ───────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sub.example.com;
ssl_certificate /etc/angie/ssl/fullchain.pem;
ssl_certificate_key /etc/angie/ssl/privkey.key;
ssl_trusted_certificate /etc/angie/ssl/fullchain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=60s;
resolver_timeout 2s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
client_max_body_size 10m;
# Статика сборки страницы подписки
location ^~ /assets/ {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;
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 https;
proxy_set_header Connection "";
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;
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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
}
# ─── Отсекаем TLS-хендшейки по IP и неизвестным доменам ──────────────
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
При установке на хост скопируйте файл в /etc/angie/http.d/xlada.conf. При
запуске в Docker он монтируется в контейнер целиком, как показано ниже.
Директиву reuseport не используем: она несовместима с несколькими
виртуальными хостами на одном адресе и порту — Angie завершится с ошибкой
duplicate listen options.
Вариант со встроенным ACME
Если внешние клиенты не нужны, Angie выпускает сертификат сам. Конфиг ниже —
самодостаточный: заранее ничего выпускать не надо, Angie получит сертификат при
первом запуске и будет продлевать его автоматически. Проверка владения доменом
идёт по TLS-ALPN на порту 443, поэтому порт 80 только перенаправляет. Состояние
ACME хранится в /var/lib/angie/acme/ — этот каталог должен переживать
перезапуск контейнера.
# ─── Upstream-ы: контейнеры xpanel и xpanel-subpage ──────────────────
upstream xpanel_panel {
server 127.0.0.1:3000;
keepalive 32;
}
upstream xpanel_subpage {
server 127.0.0.1:3010;
keepalive 16;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# ─── Встроенный ACME ─────────────────────────────────────────────────
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=60s;
resolver_timeout 2s;
acme_client acme_le https://acme-v02.api.letsencrypt.org/directory;
# ─── Gzip для всех виртуальных хостов ────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
application/wasm
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
# ─── HTTP: редирект на HTTPS ─────────────────────────────────────────
server {
listen 80;
listen [::]:80;
server_name panel.example.com sub.example.com;
location / {
return 301 https://$host$request_uri;
}
}
# ─── Панель XLADA ────────────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;
acme acme_le;
ssl_certificate $acme_cert_acme_le;
ssl_certificate_key $acme_cert_key_acme_le;
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:ACME_TLS:10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
client_max_body_size 25m;
location ^~ /assets/ {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;
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 https;
proxy_set_header Connection "";
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;
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 https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
proxy_buffering off;
proxy_cache off;
}
}
# ─── Страница подписки ───────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sub.example.com;
acme acme_le;
ssl_certificate $acme_cert_acme_le;
ssl_certificate_key $acme_cert_key_acme_le;
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:ACME_TLS:10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
client_max_body_size 10m;
location ^~ /assets/ {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;
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 https;
proxy_set_header Connection "";
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;
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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
}
# ─── Отсекаем TLS-хендшейки по IP и неизвестным доменам ──────────────
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
Проверьте конфигурацию и перезагрузите Angie:
sudo angie -t && sudo angie -s reload
docker compose exec angie angie -t && docker compose exec angie angie -s reload
Запуск в Docker
services:
angie:
image: docker.angie.software/angie:1.9.0
container_name: angie
restart: unless-stopped
# host-режим, чтобы Angie видел 127.0.0.1:3000 и 127.0.0.1:3010
network_mode: host
environment:
- TZ=UTC
volumes:
- ./angie.conf:/etc/angie/http.d/default.conf:ro
- /etc/angie/ssl:/etc/angie/ssl:ro # сертификаты Let's Encrypt
- /var/www/acme:/var/www/acme:ro # каталог ACME-проверок
- angie-ssl-data:/var/lib/angie/acme/ # состояние встроенного ACME
volumes:
angie-ssl-data:
name: angie-ssl-data
cd /opt/angie
docker compose up -d
docker compose logs -f -t
Host-режим выбран намеренно: контейнер видит те же loopback-порты
127.0.0.1:3000 и 127.0.0.1:3010, что и хост, поэтому апстримы в
angie.conf не нужно переписывать на имена контейнеров. Сертификаты и каталог
ACME-проверок при этом остаются на хосте.
Проверка и перезагрузка
# Проверка синтаксиса
sudo angie -t
docker compose exec angie angie -t
# Полный дамп итоговой конфигурации
sudo angie -T
# Мягкая перезагрузка без разрыва соединений
sudo angie -s reload
docker compose exec angie angie -s reload
Что смотреть в логах
journalctl -u angie -f # установка на хост
docker compose logs -f -t angie
[emerg]— ошибка конфигурации,angie -tпокажет строку;connect() failed (111: Connection refused)— панель или страница подписки не запущены на ожидаемом порту;upstream timed out— увеличьтеproxy_read_timeoutдля долгих операций;SSL_do_handshake() failed— обычные сканеры, реакции не требуют.
curl -I https://panel.example.com
curl -I https://sub.example.com
Оба ответа должны начинаться с HTTP/2 200. Если в аудите панели видны адреса
Cloudflare, включите доверие к CF-Connecting-IP через модуль realip.