Skip to content

Instantly share code, notes, and snippets.

@jaeyson
Created October 7, 2025 04:00
Show Gist options
  • Select an option

  • Save jaeyson/88b66ba5c6e5a96021b0a100df9a3df7 to your computer and use it in GitHub Desktop.

Select an option

Save jaeyson/88b66ba5c6e5a96021b0a100df9a3df7 to your computer and use it in GitHub Desktop.
Nginx/OpenResty configuration

You need to change ulimit (in this gist) for the www-data user, else you can't just increase worker_rlimit_nofile. Enabled HTTP2 as well as QUIC.

  • etc/security/limits.conf: OS resource limit settings.
  • location /etc/nginx
  • snippets in /etc/nginx/snippets.
  • ssl-options.conf: can also be included in server blocks running TLS.
  • nginx.conf: root config file
  • security.conf: contains security-specific config which can be added in server blocks
  • quic.conf
  • default: default fallback config.
  • domain.co.conf
  • general.conf: can be added in server blocks where it needs
  • file_cache.conf: open file cache config for caching frequent used files from disk.
  • fastcgi-php.conf: e.g. location ~ \.php(?:$|/) { include snippets/fastcgi-php.conf; }
# Default server configuration
server {
listen 80 reuseport default_server;
# listen [::]:80 default_server;
# Enable http/3
listen 443 quic reuseport default_server;
# listen [::]:443 quic reuseport default_server;
listen 443 ssl reuseport default_server;
# listen [::]:443 ssl default_server;
server_name _;
# Use the default nginx folder
root /usr/share/nginx/html;
limit_req zone=ip burst=80 delay=50;
access_log /var/log/nginx/strict_access.log timed_combined;
include snippets/ssl-options.conf;
ssl_certificate /etc/nginx/ssl/self.crt;
ssl_certificate_key /etc/nginx/ssl/self.key;
include snippets/security.conf;
index index.html index.htm;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
include snippets/general.conf;
}
server {
listen 80;
server_name domain.co;
if ($host = domain.co) {
return 301 https://$host$request_uri;
}
}
server {
listen 443 quic;
listen 443 ssl;
http2 on;
server_name domain.co;
root /var/www/domain.co/html/public;
# Allow up to burst 30 requests, first 12 are processed without delay.
# After 12 excessive requests we enforce our 2 r/s zone limit.
# After 30 excessive requests (burst) we reject the requests.
limit_req zone=ip burst=30 delay=12;
access_log /var/log/nginx/strict_access.log timed_combined;
include snippets/ssl-options.conf;
# Certificate & key
ssl_certificate /etc/letsencrypt/live/domain.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.co/privkey.pem;
include snippets/quic.conf;
include snippets/security.conf;
include snippets/file_cache.conf;
# 4 Hours private (in browser) cache
add_header Cache-Control "private, max-age=14400";
index index.html index.php;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php(?:$|/) {
include snippets/fastcgi-php.conf;
}
include snippets/general.conf;
}
# regex to split $uri to $fastcgi_script_name and $fastcgi_path
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
# Check that the PHP script exists before passing it
try_files $fastcgi_script_name =404;
# This incude file contains most of the params we need
include fastcgi.conf;
## fastcgi settings
# Set PHP handler
fastcgi_pass php_backend;
fastcgi_index index.php;
# Increase timeout
fastcgi_read_timeout 600;
fastcgi_intercept_errors on;
# Disable buffering (for uploading files)
fastcgi_request_buffering off;
# Keep connection open
fastcgi_keep_conn on;
# Increase memory size of the buffer segments (used for the payload of the response)
# 32 x 16k
fastcgi_buffers 32 16K;
# Increase memory buffer for HTTP response header
fastcgi_buffer_size 32k;
# fastcgi params
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
# Static assets
location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|tgz|gz|rar|bz2|doc|pdf|ptt|tar|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?|ttf|ttc|otf|eot|woff2?)$ {
expires 30d;
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "public, no-transform";
access_log off;
}
# Do not allow any hidden files or folders (including .htaccess or .env files and .git folder)
# Return 444, do not spent even resources
location ~ /\. {
deny all;
return 404;
access_log off;
}
# ACME-challenge
location ^~ /.well-known/acme-challenge/ {
root /var/www/_letsencrypt;
}
root soft nofile 500000
postgres soft nofile 65535
postgres hard nofile 500000
www-data soft nofile 65535
www-data hard nofile 500000
user www-data;
worker_processes auto;
worker_cpu_affinity auto;
# Change the default thread pool settings
thread_pool default threads=16 max_queue=65536;
# The max amount of open files (FDs) per worker process
# (set at least twice the value of worker_connections)
worker_rlimit_nofile 12000;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
# Determines how much clients can be served per worker process
# max clients = worker_connections * worker_processes
worker_connections 4096;
# Optimized to serve many clients with each thread, for linux only
use epoll;
# Accept as many connections as possible
multi_accept on;
}
http {
##
# Basic Settings
##
# Copies data between one FD and other from within the kernel
# faster than read() + write()
sendfile on;
# Use the default thread pool for asynchronous file I/O
aio threads;
# Only use AIO is used for when larger than or equal to this size
directio 6m;
# Send headers in one piece, it is better than sending them one by one
tcp_nopush on;
# Don't buffer data sent, good for small data bursts in real time
tcp_nodelay on;
# For security reasons don't send Nginx version in error messages or response headers
server_tokens off;
# Disable logging if a file can't be found
log_not_found off;
# Server will close connection after this time
keepalive_timeout 65s;
# Max. allowed requests on a single keepalive connection
keepalive_requests 1000;
# Max size of types hash tables (processing static sets of data. eg. server names, map directives or mime types)
types_hash_max_size 2048;
# Set bicket size of the hash tables
types_hash_bucket_size 64;
# Max allowed size of the client request body
client_max_body_size 250M;
# If the request body size is more than the buffer size, then the entire (or partial)
# request body is written into a temporary file
client_body_buffer_size 512k;
# Request timed out
client_body_timeout 300s;
# Allow the server to close connection on non responding client, this will free up memory
reset_timedout_connection on;
# Increase max number of concurrenty HTTP/3 request streams in a connection
http3_max_concurrent_streams 1024;
# Increase the size of the buffer used for reading and writing of the QUIC streams
http3_stream_buffer_size 1024k;
# Enable sending in optimized batch mode using segmentation offloading
quic_gso on;
# Enable QUIC Address Validation feature
quic_retry on;
# Do not turn off request buffering (proxy_buffering), directly pass it to the server without caching
# See also: https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#proxy_buffering-off
# Buffer the response from the backend server, which contains the headers.
proxy_buffer_size 32k;
# Buffer size of the response to the client while the response is not yet fully read
# proxy_buffer_size + 2x 4k buffers
proxy_busy_buffers_size 32k;
# xk = 128 times 16k buffering response (16k for the headers, 496k for the body response)
proxy_buffers 128 16k;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# DNS
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s ipv6=off;
resolver_timeout 4s;
##
# Rate limiting
##
# Whitelist of rate limit (incl. my external IP address)
geo $limit {
default 1;
77.61.56.117 0;
127.0.0.1 0;
192.168.1.0/24 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
# Two stage rate limit (10 MB zone): 2 requests/sec limit (=second stage)
limit_req_zone $limit_key zone=ip:10m rate=2r/s;
# Dedicated limit for Synapse server
limit_req_zone $limit_key zone=matrix_limit:10m rate=5r/s;
# First stage (burst) can be found in the individual virtual server configs
##
# Logging Settings
##
# Discard 2xx or 3xx responses from logging
# Fail2Ban rate limit filter still works, because it checks on Nginx error log
map $status $loggable {
~^[23] 0;
default 1;
}
log_format timed_combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$host" $request_time';
access_log /var/log/nginx/access.log timed_combined if=$loggable;
# Show warn, error, crit, alert and emerg messages
error_log /var/log/nginx/error.log warn;
##
# Gzip Settings
# Reduce the data that needs to be sent over network
##
gzip on;
gzip_disable msie6;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types
text/css
text/plain
text/javascript
text/cache-manifest
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy
application/javascript
application/json
application/x-javascript
application/ld+json
application/xml
application/xml+rss
application/xhtml+xml
application/x-font-ttf
application/x-font-opentype
application/vnd.ms-fontobject
application/manifest+json
application/rss+xml
application/atom_xml
application/vnd.geo+json
application/x-web-app-manifest+json
image/svg+xml
image/x-icon
image/bmp
font/opentype;
##
# Use upstream block for PHP
#
upstream php_backend {
server unix:/var/run/php/php-fpm.sock;
keepalive 8;
}
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
# Add quic headers
add_header Alt-Svc 'h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400' always;
add_header x-quic 'h3' always;
# Don't leak powered-by
fastcgi_hide_header X-Powered-By;
# Security headers
# Don't allow the browser to render the page inside an frame or iframe and avoid clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Enable the Cross-site scripting (XSS) filter built into most recent web browsers.
add_header X-XSS-Protection "1; mode=block" always;
# When serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
add_header X-Content-Type-Options "nosniff" always;
# Referrer Policy will allow a site to control the value of the referer header in links away from their pages.
add_header Referrer-Policy "no-referrer" always;
# Disable the option to open a file directly on download
add_header X-Download-Options "noopen" always;
# Don't allow cross domain of Falsh & PDF documents
add_header X-Permitted-Cross-Domain-Policies "none" always;
# Feature to support on your site and strengthens your implementation of TLS by getting the User Agent to enforce the use of HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Restore IP behind proxy
real_ip_header X-Real-IP; ## X-Real-IP or X-Forwarded-For or proxy_protocol
real_ip_recursive off; ## If you enable 'on'
# Only enable TLS v1.2 and v1.3
# With only safe ciphers
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_early_data on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_ecdh_curve secp521r1:secp384r1:secp256k1;
# Increase security (using the Diffie-Hellman Group file)
ssl_dhparam /etc/nginx/dhparam.pem;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment