Created
May 7, 2026 04:12
-
-
Save alexishida/b71bdf593101e8b985c0fc509c6f04f3 to your computer and use it in GitHub Desktop.
Arquivo de configuração do nginx focado em segurança para WordPress, com as principais proteções contra ataques comuns.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Mitigações de ataque | |
| Brute-force de login: rate limit em /wp-login.php (5 req/min) e bloqueio total do xmlrpc.php (vetor clássico de amplificação e brute-force via system.multicall). | |
| Enumeração de usuários: bloqueia ?author=N. | |
| Path traversal / SQLi / XSS via query string: WAF leve com regex. | |
| Upload malicioso: bloqueia execução de .php em wp-content/uploads, wp-includes e cache. | |
| Information disclosure: oculta readme.html, license.txt, wp-config-sample.php, .env, .git, dumps SQL, logs. | |
| Slowloris / DoS leve: timeouts curtos e limit_conn. | |
| Bots ofensivos (wpscan, sqlmap, nikto): bloqueio por User-Agent. | |
| Httpoxy (CVE-2016-5385): HTTP_PROXY "" no fastcgi. | |
| Headers de segurança: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. | |
| TLS: só TLS 1.2/1.3, ciphers modernos, OCSP stapling. | |
| Pontos que você precisa ajustar antes de subir: | |
| As 4 zonas de limit_req_zone e os dois map precisam ir no http {} do nginx.conf (deixei comentado no topo). | |
| Caminho do socket PHP-FPM (php8.2-fpm.sock) — confira sua versão. | |
| Domínio, root e certificados. | |
| Se usar Jetpack ou app móvel do WP, descomente a alternativa do xmlrpc.php em vez do bloqueio total. | |
| A CSP está permissiva (unsafe-inline/unsafe-eval) porque WP + plugins quebram com CSP estrita — recomendo subir primeiro como Content-Security-Policy-Report-Only e ir apertando. | |
| Complementos que recomendo fora do nginx, já que isto cobre só a camada de borda: | |
| fail2ban com jail para wordpress.access.log (404 em massa, 401, POST em wp-login). | |
| Permissões de arquivo: wp-config.php 640, owner separado do usuário do PHP-FPM. | |
| DISALLOW_FILE_EDIT e DISALLOW_FILE_MODS no wp-config.php. | |
| Auto-update de core/plugins ou pipeline de patching. | |
| Backup off-site testado. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ===================================================================== | |
| # Configuração Nginx Hardened para WordPress | |
| # Foco: mitigação de ataques comuns (XSS, SQLi, brute-force, enumeração, | |
| # upload malicioso, hotlinking, exploração de plugins/themes, etc.) | |
| # | |
| # Requer: nginx >= 1.18, PHP-FPM, fail2ban (recomendado), certbot/SSL. | |
| # Ajuste os caminhos (root, fastcgi_pass, server_name, certificados). | |
| # ===================================================================== | |
| # --------------------------------------------------------------------- | |
| # 1) Rate limiting zones (declarar no contexto http {} do nginx.conf) | |
| # --------------------------------------------------------------------- | |
| # Adicione no /etc/nginx/nginx.conf dentro do bloco http { } : | |
| # | |
| # limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m; | |
| # limit_req_zone $binary_remote_addr zone=wp_general:10m rate=30r/s; | |
| # limit_req_zone $binary_remote_addr zone=wp_xmlrpc:10m rate=1r/m; | |
| # limit_conn_zone $binary_remote_addr zone=wp_conn:10m; | |
| # | |
| # # Mapa para bloquear user-agents indesejados | |
| # map $http_user_agent $bad_bot { | |
| # default 0; | |
| # ~*(nikto|sqlmap|wpscan) 1; | |
| # ~*(masscan|nmap|zgrab) 1; | |
| # ~*(libwww|curl|wget) 1; # remova se usar webhooks legítimos | |
| # "" 1; # UA vazio | |
| # } | |
| # | |
| # # Mapa para bloquear referers de spam | |
| # map $http_referer $bad_referer { | |
| # default 0; | |
| # ~*(viagra|casino|porn) 1; | |
| # } | |
| # --------------------------------------------------------------------- | |
| # 2) Redirect HTTP -> HTTPS | |
| # --------------------------------------------------------------------- | |
| server { | |
| listen 80; | |
| listen [::]:80; | |
| server_name exemplo.com www.exemplo.com; | |
| # Permite ACME challenge (certbot) sem redirect | |
| location ^~ /.well-known/acme-challenge/ { | |
| root /var/www/letsencrypt; | |
| default_type "text/plain"; | |
| allow all; | |
| } | |
| location / { | |
| return 301 https://$host$request_uri; | |
| } | |
| } | |
| # --------------------------------------------------------------------- | |
| # 3) Server block principal HTTPS | |
| # --------------------------------------------------------------------- | |
| server { | |
| listen 443 ssl http2; | |
| listen [::]:443 ssl http2; | |
| server_name exemplo.com www.exemplo.com; | |
| root /var/www/wordpress; | |
| index index.php; | |
| # ----- SSL/TLS ----- | |
| ssl_certificate /etc/letsencrypt/live/exemplo.com/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/exemplo.com/privkey.pem; | |
| ssl_trusted_certificate /etc/letsencrypt/live/exemplo.com/chain.pem; | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; | |
| ssl_prefer_server_ciphers on; | |
| ssl_session_cache shared:SSL:10m; | |
| ssl_session_timeout 1d; | |
| ssl_session_tickets off; | |
| ssl_stapling on; | |
| ssl_stapling_verify on; | |
| resolver 1.1.1.1 8.8.8.8 valid=300s; | |
| resolver_timeout 5s; | |
| # ----- Logs ----- | |
| access_log /var/log/nginx/wordpress.access.log; | |
| error_log /var/log/nginx/wordpress.error.log warn; | |
| # ----- Tamanhos e timeouts (mitigação de slowloris/DoS) ----- | |
| client_max_body_size 32m; | |
| client_body_buffer_size 128k; | |
| client_body_timeout 10s; | |
| client_header_timeout 10s; | |
| keepalive_timeout 30s; | |
| send_timeout 10s; | |
| server_tokens off; # esconde versão do nginx | |
| more_clear_headers Server; # requer headers-more-nginx-module (opcional) | |
| # ----- Security headers ----- | |
| add_header X-Frame-Options "SAMEORIGIN" always; | |
| add_header X-Content-Type-Options "nosniff" always; | |
| add_header X-XSS-Protection "1; mode=block" always; | |
| add_header Referrer-Policy "strict-origin-when-cross-origin" always; | |
| add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; | |
| add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; | |
| # CSP: ajuste conforme plugins/temas. Comece em report-only para não quebrar o site. | |
| add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self';" always; | |
| # ----- Bloqueios baseados em mapas ----- | |
| if ($bad_bot) { return 444; } | |
| if ($bad_referer) { return 444; } | |
| # Bloqueia métodos HTTP não usados | |
| if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) { | |
| return 444; | |
| } | |
| # Limite de conexões simultâneas por IP | |
| limit_conn wp_conn 20; | |
| # ================================================================= | |
| # Bloqueios de paths sensíveis | |
| # ================================================================= | |
| # Arquivos ocultos (.git, .env, .htaccess, etc.) | |
| location ~ /\.(?!well-known) { | |
| deny all; | |
| access_log off; | |
| log_not_found off; | |
| return 404; | |
| } | |
| # Arquivos de backup, dump, log, configs | |
| location ~* \.(bak|backup|swp|save|old|orig|original|tmp|sql|log|conf|ini|env|yml|yaml)$ { | |
| deny all; | |
| return 404; | |
| } | |
| # Bloqueia README, LICENSE, CHANGELOG do WP/plugins (revelam versões) | |
| location ~* /(readme|license|changelog|wp-config-sample)\.(txt|html|md)$ { | |
| deny all; | |
| return 404; | |
| } | |
| # wp-config.php nunca pode ser servido | |
| location = /wp-config.php { deny all; return 404; } | |
| # Diretórios críticos do WP | |
| location ~* /wp-content/uploads/.*\.(php|phtml|phar|pl|py|jsp|asp|sh|cgi)$ { | |
| deny all; | |
| return 403; | |
| } | |
| location ~* /wp-content/plugins/.*\.(sql|log|txt|md)$ { | |
| deny all; | |
| return 403; | |
| } | |
| location ~* /wp-content/themes/.*\.(sql|log|txt|md)$ { | |
| deny all; | |
| return 403; | |
| } | |
| # Bloqueia execução de PHP em diretórios que não devem executar | |
| location ~* ^/wp-content/uploads/.*\.php$ { deny all; return 403; } | |
| location ~* ^/wp-includes/.*\.php$ { deny all; return 403; } | |
| location ~* ^/wp-content/cache/.*\.php$ { deny all; return 403; } | |
| # Enumeração de usuários via ?author=N | |
| if ($args ~* "author=\d+") { | |
| return 403; | |
| } | |
| # Bloqueia install.php e upgrade.php após instalação (libere temporariamente se precisar) | |
| location = /wp-admin/install.php { deny all; return 403; } | |
| location = /wp-admin/upgrade.php { deny all; return 403; } | |
| # ================================================================= | |
| # XML-RPC: principal vetor de brute-force e amplificação | |
| # Recomendado: bloquear totalmente. Se precisar (Jetpack/app móvel), | |
| # comente o "deny all" e use o limit_req abaixo. | |
| # ================================================================= | |
| location = /xmlrpc.php { | |
| deny all; | |
| access_log off; | |
| log_not_found off; | |
| return 444; | |
| # --- alternativa com rate limit --- | |
| # limit_req zone=wp_xmlrpc burst=2 nodelay; | |
| # include fastcgi_params; | |
| # fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| } | |
| # ================================================================= | |
| # wp-login.php e wp-admin: rate limit + (opcional) restrição de IP | |
| # ================================================================= | |
| location = /wp-login.php { | |
| limit_req zone=wp_login burst=2 nodelay; | |
| # Descomente para restringir login a IPs corporativos: | |
| # allow 203.0.113.0/24; | |
| # deny all; | |
| include fastcgi_params; | |
| fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| fastcgi_intercept_errors on; | |
| fastcgi_buffers 16 16k; | |
| fastcgi_buffer_size 32k; | |
| } | |
| location ^~ /wp-admin/ { | |
| limit_req zone=wp_general burst=20 nodelay; | |
| # Descomente para restringir wp-admin por IP: | |
| # allow 203.0.113.0/24; | |
| # deny all; | |
| location ~ \.php$ { | |
| try_files $uri =404; | |
| include fastcgi_params; | |
| fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| fastcgi_param HTTP_PROXY ""; # mitigação httpoxy | |
| } | |
| } | |
| # admin-ajax precisa ficar acessível (muitos plugins usam no front) | |
| location = /wp-admin/admin-ajax.php { | |
| limit_req zone=wp_general burst=50 nodelay; | |
| include fastcgi_params; | |
| fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| fastcgi_param HTTP_PROXY ""; | |
| } | |
| # ================================================================= | |
| # WAF leve via regex em query string e URI | |
| # ================================================================= | |
| set $block_sql_injections 0; | |
| if ($query_string ~* "union.*select.*\(") { set $block_sql_injections 1; } | |
| if ($query_string ~* "concat.*\(") { set $block_sql_injections 1; } | |
| if ($query_string ~* "(<|%3C).*script.*(>|%3E)") { set $block_sql_injections 1; } | |
| if ($query_string ~* "base64_(en|de)code\(") { set $block_sql_injections 1; } | |
| if ($query_string ~* "(eval\(|GLOBALS=|REQUEST=)") { set $block_sql_injections 1; } | |
| if ($query_string ~* "\.\./") { set $block_sql_injections 1; } | |
| if ($block_sql_injections = 1) { | |
| return 403; | |
| } | |
| # ================================================================= | |
| # Roteamento WordPress padrão | |
| # ================================================================= | |
| location / { | |
| limit_req zone=wp_general burst=50 nodelay; | |
| try_files $uri $uri/ /index.php?$args; | |
| } | |
| # PHP handler (somente para arquivos que existem) | |
| location ~ \.php$ { | |
| try_files $uri =404; | |
| fastcgi_split_path_info ^(.+\.php)(/.+)$; | |
| include fastcgi_params; | |
| fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| fastcgi_index index.php; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| fastcgi_param PATH_INFO $fastcgi_path_info; | |
| fastcgi_param HTTP_PROXY ""; | |
| # Evita execução de PHP via upload com extensão dupla (foo.jpg.php) | |
| fastcgi_param REDIRECT_STATUS 200; | |
| fastcgi_read_timeout 60s; | |
| fastcgi_buffers 16 16k; | |
| fastcgi_buffer_size 32k; | |
| fastcgi_intercept_errors on; | |
| } | |
| # ================================================================= | |
| # Cache e hardening de assets estáticos | |
| # ================================================================= | |
| location ~* \.(jpg|jpeg|png|gif|webp|svg|ico|css|js|woff2?|ttf|eot|otf|mp4|webm)$ { | |
| expires 30d; | |
| add_header Cache-Control "public, immutable"; | |
| access_log off; | |
| try_files $uri =404; | |
| # Anti hotlinking (opcional - descomente e ajuste o domínio) | |
| # valid_referers none blocked exemplo.com *.exemplo.com; | |
| # if ($invalid_referer) { return 403; } | |
| } | |
| # Robots e favicon | |
| location = /favicon.ico { log_not_found off; access_log off; } | |
| location = /robots.txt { log_not_found off; access_log off; allow all; } | |
| # Página de erro genérica (não vaza stack) | |
| error_page 500 502 503 504 /50x.html; | |
| location = /50x.html { internal; } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment