Skip to content

Instantly share code, notes, and snippets.

@rubendob
Created October 3, 2025 08:09
Show Gist options
  • Select an option

  • Save rubendob/fac1db2090ab4e543e280719f9ec0a21 to your computer and use it in GitHub Desktop.

Select an option

Save rubendob/fac1db2090ab4e543e280719f9ec0a21 to your computer and use it in GitHub Desktop.
Nginx for Wordpress
server {
listen 443 ssl;
access_log /var/log/nginx/access.log main;
# Use a generic placeholder, not your real domain
server_name example.com;
# Replace with your real certificate paths (kept generic for sharing)
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
# TLS hardening (keep modern protocols/ciphers only)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:1m; # ~4000 sessions
ssl_session_timeout 1h; # Session reuse window
ssl_session_tickets off; # Disable tickets if you can rotate keys
ssl_buffer_size 4k; # Reasonable TLS record size
ssl_prefer_server_ciphers on;
# Adjust if you upload large media via WP
client_max_body_size 128M;
root /var/www/html/;
index index.php;
# -------------------------
# Security headers
# -------------------------
# Clickjacking protection: allow framing only from same origin
add_header X-Frame-Options "SAMEORIGIN" always;
# MIME sniffing protection
add_header X-Content-Type-Options "nosniff" always;
# HSTS: force HTTPS for one year including subdomains (enable only once HTTPS is stable)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Referrer policy: send full URL on same-origin, only origin on cross-origin
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions-Policy: disable browser features not needed by WP frontends
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), gyroscope=(), magnetometer=(), accelerometer=(), fullscreen=(), interest-cohort=()" always;
# -------------------------
# General routing
# -------------------------
location / {
# Try static file first, then directory, then route to index.php with query string
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
autoindex off;
}
# Cache static assets for 30 days (adjust to your cache strategy/CDN)
location ~* \.(css|js|jpg|jpeg|png|gif|ico|txt|woff|woff2|ttf)$ {
expires 30d;
}
# -------------------------
# WordPress-specific hardening
# -------------------------
# Limit access to xmlrpc: allow only a trusted IP (replace with your own or deny all)
location = /xmlrpc.php {
allow 203.0.113.10; # <--- replace or remove 'allow' and just 'deny all;'
deny all;
}
# Deny direct access to scripts inside common WP directories
location ~* /(?:uploads|files|wp-content|wp-includes|akismet)/.*\.(?:html?|php|js|swf)$ {
deny all;
access_log off;
log_not_found off;
}
# Extra protection in uploads: block script-like files
location ~* ^/wp-content/uploads/.*\.(?:s?html?|php|js|swf)$ {
deny all;
}
# Hide VCS/hidden files (.git, .svn, etc.)
location ~ /\.(?:svn|git)/* {
deny all;
access_log off;
log_not_found off;
}
# Block direct access to .htaccess if present (Apache artifacts)
location ~ /\.htaccess {
deny all;
access_log off;
log_not_found off;
}
# Block PHP/user ini files
location ~ /\.user\.ini {
deny all;
access_log off;
log_not_found off;
}
# Protect sensitive WP core files
location ~* /(wp-config\.php|wp-cron\.php) {
deny all;
}
# Disallow execution of non-PHP scripts from document root (common web shells)
location ~* \.(?:pl|cgi|py|sh|lua|asp)$ {
return 444;
}
# Prevent plugin folder reconnaissance (block text/log/markdown listings)
location ~* ^/wp-content/plugins/.+\.(?:txt|log|md)$ {
deny all;
error_page 403 =404 /;
}
# Prevent theme folder reconnaissance
location ~* ^/wp-content/themes/.+\.(?:txt|log|md)$ {
deny all;
error_page 403 =404 /;
}
# Block access to installer/upgrade scripts (should never be directly reachable)
location = /wp-admin/install.php {
deny all;
error_page 403 =404 /;
}
location = /wp-admin/upgrade.php {
deny all;
error_page 403 =404 /;
}
# Deny backup/log/temp/DB dump extensions
location ~* ^.+\.(?:bak|log|old|orig|original|php~|php_bak|save|swo|swp|sql)$ {
deny all;
access_log off;
log_not_found off;
}
# Block common public files that should not be served
location ~* ^/(?:xmlrpc\.php|wp-links-opml\.php|wp-config\.php|wp-config-sample\.php|readme\.html|license\.txt)$ {
deny all;
}
# -------------------------
# PHP handling (php-fpm upstream)
# -------------------------
location ~ \.(?:php|phar)(/.*)?$ {
fastcgi_split_path_info ^(.+\.(?:php|phar))(/.*)$;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 30s;
fastcgi_read_timeout 30s;
# Standard FastCGI params
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# Required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
# Map script to real path and pass PATH_INFO
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Upstream name must match your upstream config (e.g., in http{}: upstream php-fpm { server 127.0.0.1:9000; })
fastcgi_pass php-fpm;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment