В этой статье мы рассмотрим как поднять стек Caddy (HTTPS, HTTP/2/3) → Nginx (fastcgi_cache + security) → WordPress-PHP-FPM → MariaDB → Redis. Реальный IP клиента берём через X-Forwarded-For от Caddy. Добавим системные тюнинги (io_uring, THP, QUIC-буферы) и чек-лист проверки.
Документация: Docker / Compose, Nginx, Caddy, WordPress, MariaDB, Redis.
Подготовка каталога и файлов
Заведомо установите
DockerиDocker Compose
Создайте необходимые директории:
mkdir -p /opt/wordpress/{caddy,nginx,php,conf.d} && cd /opt/wordpress
Структура:
project-root/
│
├─ docker-compose.yml
│
├─ php/
│ └─ uploads.ini
│
├─ nginx/
│ ├─ nginx.conf
│ └─ conf.d/
│ └─ wordpress.conf
│
└─ caddy/
└─ Caddyfile
📌 Пояснения:
php/uploads.ini— задаёт лимиты (загрузка, память, время выполнения).nginx/nginx.conf— основная конфигурация Nginx (включает fastcgi_cache, gzip и логи).nginx/conf.d/wordpress.conf— отдельный виртуальный хост WordPress с правилами кэша и безопасностью.caddy/Caddyfile— прокси с HTTPS и автообновлением сертификатов Let’s Encrypt.
Конфигурации
docker-compose.yml (ключевые моменты)
Nginx монтируем папку
conf.d(чтобы скрыть дефолтныйdefault.confиз образа).Caddy: принимает 80/443, проксирует на
nginx:80и прокидывает реальный IP клиента.WordPress:
php-fpmобраз,WORDPRESS_CONFIG_EXTRAс базовыми дефайнами.Redis: для object-cache.
MariaDB: LTS, utf8mb4.
(ниже — только важные элементы; свои пароли/домены подставьте)
services:
db:
image: mariadb:10.11
command:
- --innodb-buffer-pool-size=256M
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb-file-per-table=1
- --server-id=1
- --log-bin=mysql-bin
- --binlog-expire-logs-seconds=604800
environment:
MYSQL_ROOT_PASSWORD: '<root_password>'
MYSQL_DATABASE: stm_wordpress
MYSQL_USER: stm_wordpress_user
MYSQL_PASSWORD: '<db_password>'
TZ: Europe/Amsterdam
volumes:
- db_data:/var/lib/mysql
restart: always
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
restart: always
wordpress:
image: wordpress:6.8.3-php8.3-fpm
depends_on:
- db
- redis
environment:
WORDPRESS_DB_HOST: "db:3306"
WORDPRESS_DB_USER: stm_wordpress_user
WORDPRESS_DB_PASSWORD: '<wp_db_password>'
WORDPRESS_DB_NAME: stm_wordpress
TZ: Europe/Amsterdam
WORDPRESS_CONFIG_EXTRA: |
if (!defined('WP_CACHE')) define('WP_CACHE', true);
if (!defined('DISALLOW_FILE_EDIT')) define('DISALLOW_FILE_EDIT', true);
if (!defined('AUTOMATIC_UPDATER_DISABLED')) define('AUTOMATIC_UPDATER_DISABLED', true);
if (!defined('FS_METHOD')) define('FS_METHOD', 'direct');
if (!defined('WP_MEMORY_LIMIT')) define('WP_MEMORY_LIMIT', '256M');
if (!defined('WP_MAX_MEMORY_LIMIT')) define('WP_MAX_MEMORY_LIMIT', '256M');
if (!defined('WP_DEBUG')) define('WP_DEBUG', false);
if (!defined('WP_POST_REVISIONS')) define('WP_POST_REVISIONS', 5);
if (!defined('EMPTY_TRASH_DAYS')) define('EMPTY_TRASH_DAYS', 7);
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
/* HTTPS за прокси (Caddy) */
if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$$_SERVER['HTTPS'] = 'on';
}
volumes:
- wp_data:/var/www/html
- ./php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
restart: always
nginx:
image: nginx:1.27-alpine
depends_on:
- wordpress
# Порты наружу НЕ открываем — к нему ходит только Caddy
volumes:
- wp_data:/var/www/html:ro
- nginx_cache:/var/cache/nginx
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d/wordpress.conf:/etc/nginx/conf.d/wordpress.conf:ro
restart: always
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
caddy:
image: caddy:2.8-alpine
depends_on:
- nginx
ports:
- "80:80"
- "443:443"
environment:
DOMAIN: your.site
ACME_EMAIL: admin@your.site
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
restart: always
volumes:
db_data:
wp_data:
nginx_cache:
caddy_data:
caddy_config:Где:
- DOMAIN: - ваш сайт (домен)
- ACME_EMAIL: admin@ваш сайт (домен)
php/uploads.ini
file_uploads=On
memory_limit=256M
upload_max_filesize=64M
post_max_size=64M
max_execution_time=120
max_input_vars=5000
nginx/nginx.conf
user nginx;
worker_processes auto;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript application/xml+rss application/xml application/x-javascript image/svg+xml;
gzip_vary on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
set_real_ip_from 172.18.0.0/16; #your docker IP
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for" realip=$realip_remote_addr';
access_log /var/log/nginx/access.log main;
include /etc/nginx/fastcgi_params;
include /etc/nginx/conf.d/*.conf;
}
nginx/conf.d/wordpress.conf (fastcgi_cache + security)
server {
listen 80 default_server;
server_name _;
root /var/www/html;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()" always;
add_header X-XSS-Protection "1; mode=block" always;
index index.php index.html;
location ~* /(\.git|\.svn|\.hg|composer\.json|composer\.lock|package\.json|gulpfile\.js|Gruntfile\.js|webpack\.config\.js) { deny all; }
location ~* /(wp-content|wp-includes|uploads|files)/.*\.php$ { deny all; }
location = /xmlrpc.php { deny all; }
# micro-cache
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-login.php|preview=true") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wordpress_logged_in") { set $skip_cache 1; }
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|webp|ico|ttf|otf|woff|woff2)$ {
access_log off; log_not_found off;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 1m;
fastcgi_cache_valid 404 1m;
add_header X-FastCGI-Cache $upstream_cache_status;
}
if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 405; }
}
caddy/Caddyfile (TLS + реальный IP → Nginx)
{
email {$ACME_EMAIL}
}
{$DOMAIN} {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob:"
Referrer-Policy "strict-origin-when-cross-origin"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()"
}
reverse_proxy nginx:80 {
header_up X-Forwarded-For {remote}
header_up X-Real-IP {remote}
header_up Host {host}
}
}
:80 {
encode zstd gzip
redir https://{$DOMAIN}{uri} permanent
}
Запуск
docker compose up -d
Откройте https://<ваш_домен> → завершите установку WordPress.

В админке установите плагин, Redis Object Cache, включите Redis Object Cache → Enable.


Проверка real IP
docker compose exec nginx nginx -t
docker compose logs --tail=100 nginx

В access.log ожидаем:
"$http_x_forwarded_for"— не пустой;realip=<публичный_IP_клиента>(не172.18.*).
Если видите 172.18.*:
Проверьте, что Caddy отправляет
X-Forwarded-For {remote};В
nginx.confнет дублейreal_ip_header/real_ip_recursive;В
docker-compose.ymlмонтируется директорияnginx/conf.d(а неdefault.confиз образа).
Производительность и стабильность хоста (опционально)
Redis: overcommit + THP off (постоянно)
echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/90-redis.conf
sudo sysctl --system
Transparent Huge Pages: постоянное отключение systemd-юнитом:
sudo tee /etc/systemd/system/disable-thp.service >/dev/null <<'EOF'
[Unit]
Description=Disable Transparent Huge Pages
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
Перезапуск/применение:
sudo systemctl daemon-reload
sudo systemctl enable --now disable-thp.service
Проверка:
cat /sys/kernel/mm/transparent_hugepage/enabled # [...] [never]
cat /sys/kernel/mm/transparent_hugepage/defrag # [...] [never]
MariaDB: io_uring
# лучший вариант: включить
sudo sysctl kernel.io_uring_disabled=0
echo "kernel.io_uring_disabled = 0" | sudo tee /etc/sysctl.d/99-io-uring.conf
sudo sysctl --system
# проверка
sysctl kernel.io_uring_disabled # 0
Caddy: HTTP/3 (QUIC) — увеличить UDP-буферы
echo -e "net.core.rmem_max=7500000\nnet.core.wmem_max=7500000\nnet.core.rmem_default=262144\nnet.core.wmem_default=262144" | sudo tee /etc/sysctl.d/90-caddy-quic.conf
sudo sysctl --system
docker compose restart caddy
Плагины WordPress (минимум)
Redis Object Cache — обязательно.
Limit Login Attempts Reloaded — защита
/wp-login.php.WP Crontrol, Health Check & Troubleshooting — диагностика.
Не ставьте сторонние page-cache (W3TC/LiteSpeed/etc.) — кеш уже делает Nginx (fastcgi_cache).
Если позже включите Cloudflare
Переключите Nginx на
real_ip_header CF-Connecting-IPи прокиньте его из Caddy, или добавьте все CIDR Cloudflare вset_real_ip_fromи оставьтеX-Forwarded-For.
Резюме
Реальный IP без Cloudflare: Caddy выставляет
X-Forwarded-For {remote}, Nginx доверяет docker-мосту и читает IP из XFF.HTTPS/HTTP2/3 и автосертификаты Let’s Encrypt — у Caddy.
Производительность: Nginx fastcgi_cache + Redis object-cache, тюнинги хоста для стабильности.
Логи показывают корректный IP клиента, rate-limit/WAF/аналитика работают правильно.