Created
August 23, 2025 14:09
-
-
Save josuamarcelc/565b007f77659bb8acd73bd4ee1af86b to your computer and use it in GitHub Desktop.
BEST PRACTICE Advanced Security & DDoS Protection ON NGINX WITH CODEIGNITER 3
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
| # Advanced Security & DDoS Protection | |
| # Rate limiting zones | |
| limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; | |
| limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; | |
| limit_req_zone $binary_remote_addr zone=search:10m rate=20r/m; | |
| limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s; | |
| # Connection limiting | |
| limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; | |
| # Upstream for load balancing (if needed) | |
| upstream xyzid_backend { | |
| server 127.0.0.1:9000; | |
| keepalive 32; | |
| } | |
| # Main server block | |
| server { | |
| listen 80; | |
| listen [::]:80; | |
| server_name xyz.id www.xyz.id; | |
| # 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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self';" always; | |
| add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; | |
| # Hide server version | |
| server_tokens off; | |
| # Connection limits | |
| limit_conn conn_limit_per_ip 10; | |
| # Block bad bots | |
| if ($http_user_agent ~* (bot|crawler|spider|scraper)) { | |
| return 403; | |
| } | |
| # Block suspicious requests | |
| if ($request_uri ~* "\.(php|asp|aspx|jsp|cgi)$") { | |
| return 404; | |
| } | |
| # Block common attack patterns | |
| if ($request_uri ~* "(eval|base64|union|select|insert|drop|delete|update|exec|system|shell|cmd|wget|curl)") { | |
| return 444; | |
| } | |
| # Redirect HTTP to HTTPS | |
| return 301 https://$server_name$request_uri; | |
| } | |
| # HTTPS server block | |
| server { | |
| listen 443 ssl http2; | |
| listen [::]:443 ssl http2; | |
| server_name xyz.id www.xyz.id; | |
| ssl_certificate /etc/letsencrypt/live/xyz.id/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/xyz.id/privkey.pem; | |
| ssl_session_timeout 1d; | |
| ssl_session_cache shared:SSL:50m; | |
| ssl_session_tickets off; | |
| # Modern SSL configuration | |
| 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; | |
| ssl_prefer_server_ciphers off; | |
| # HSTS | |
| add_header Strict-Transport-Security "max-age=63072000" always; | |
| # 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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self';" always; | |
| add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; | |
| # Hide server version | |
| server_tokens off; | |
| # Connection limits | |
| limit_conn conn_limit_per_ip 10; | |
| # Root directory | |
| root /var/www/xyz.id; | |
| index index.php index.html; | |
| # Gzip compression | |
| gzip on; | |
| gzip_vary on; | |
| gzip_min_length 1024; | |
| gzip_proxied any; | |
| gzip_comp_level 6; | |
| gzip_types | |
| text/plain | |
| text/css | |
| text/xml | |
| text/javascript | |
| application/json | |
| application/javascript | |
| application/xml+rss | |
| application/atom+xml | |
| image/svg+xml; | |
| # File upload limits | |
| client_max_body_size 10M; | |
| client_body_timeout 60s; | |
| client_header_timeout 60s; | |
| # Block bad bots | |
| if ($http_user_agent ~* (bot|crawler|spider|scraper)) { | |
| return 403; | |
| } | |
| # Block suspicious requests | |
| if ($request_uri ~* "\.(php|asp|aspx|jsp|cgi)$") { | |
| return 404; | |
| } | |
| # Block common attack patterns | |
| if ($request_uri ~* "(eval|base64|union|select|insert|drop|delete|update|exec|system|shell|cmd|wget|curl)") { | |
| return 444; | |
| } | |
| # Rate limiting for different endpoints | |
| location /admin/ { | |
| limit_req zone=api burst=5 nodelay; | |
| limit_req zone=general burst=10 nodelay; | |
| try_files $uri $uri/ /index.php?$query_string; | |
| } | |
| location /seller/ { | |
| limit_req zone=general burst=15 nodelay; | |
| try_files $uri $uri/ /index.php?$query_string; | |
| } | |
| location /api/ { | |
| limit_req zone=api burst=5 nodelay; | |
| try_files $uri $uri/ /index.php?$query_string; | |
| } | |
| location /search { | |
| limit_req zone=search burst=10 nodelay; | |
| try_files $uri $uri/ /index.php?$query_string; | |
| } | |
| # Static files | |
| location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | |
| expires 1y; | |
| add_header Cache-Control "public, immutable"; | |
| add_header X-Content-Type-Options "nosniff"; | |
| } | |
| # PHP processing | |
| location ~ \.php$ { | |
| limit_req zone=api burst=5 nodelay; | |
| fastcgi_pass xyzid_backend; | |
| fastcgi_index index.php; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| include fastcgi_params; | |
| # FastCGI timeouts | |
| fastcgi_read_timeout 300; | |
| fastcgi_connect_timeout 60; | |
| fastcgi_send_timeout 60; | |
| # Security headers for PHP | |
| fastcgi_param HTTP_X_FORWARDED_PROTO https; | |
| fastcgi_param HTTP_X_FORWARDED_SSL on; | |
| } | |
| # Deny access to sensitive files | |
| location ~ /\. { | |
| deny all; | |
| access_log off; | |
| log_not_found off; | |
| } | |
| location ~ ~$ { | |
| deny all; | |
| access_log off; | |
| log_not_found off; | |
| } | |
| location ~ \.(htaccess|htpasswd|ini|log|sh|sql|conf)$ { | |
| deny all; | |
| access_log off; | |
| log_not_found off; | |
| } | |
| # Main location block | |
| location / { | |
| limit_req zone=general burst=20 nodelay; | |
| try_files $uri $uri/ /index.php?$query_string; | |
| } | |
| # Error pages | |
| error_page 404 /404.html; | |
| error_page 500 502 503 504 /50x.html; | |
| location = /50x.html { | |
| root /usr/share/nginx/html; | |
| } | |
| # Access and error logs | |
| access_log /var/log/nginx/xyz.id.access.log combined buffer=512k flush=1m; | |
| error_log /var/log/nginx/xyz.id.error.log warn; | |
| location ^~ /.well-known/acme-challenge/ { | |
| default_type "text/plain"; | |
| root /var/www/xyz.id; # any readable path; certbot overrides when using --nginx | |
| allow all; | |
| # Bypass rate limits / WAF-y rules for ACME | |
| limit_req off; | |
| limit_conn conn_limit_per_ip 0; | |
| add_header X-ACME-Bypass "true" always; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment