Перейти к основному содержимому

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-провайдера или регистратора:

ТипИмяЗначение
ApanelIPv4-адрес сервера
AsubIPv4-адрес сервера
AAAApanelIPv6-адрес сервера
AAAAsubIPv6-адрес сервера

В 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
/opt/angie/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/ — этот каталог должен переживать перезапуск контейнера.

/opt/angie/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;
}

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​

/opt/angie/docker-compose.yml
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.

Что дальше​