Nginx Common Configurations

Reverse Proxy

HTTP

http {
...
server {
listen 80;
server_name myserver.com;
# The default root is /usr/share/nginx/www, /usr/share/nginx/html or /var/www/html
root /var/www/your_domain/html;

# Staic files
location / {
# redefine the root
root /var/www/your_domain/html;
try_files $uri $uri/index.html =404;
# if not found redirect to home page $root/index.html
# try_files $uri $uri/ /index.html;
}

# API
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Mapping http(s)://my-domain.com/api/xxx to http://localhost:8080/api/xxx.
proxy_pass http://localhost:8080/api/;
# Mapping http(s)://my-domain.com/api/xxx to https://localhost:8080/xxx.
# proxy_pass http://localhost:8080/;
}

# Cache js, css, image, etc.
}
...
}
  • Response static files: {root path}/requestURI
  • Proxy requests: {proxy_pass path}/requestURI

Passing Request Headers

By default, NGINX redefines two header fields in proxied requests, “Host” and “Connection”, and eliminates the header fields whose values are empty strings. “Host” is set to the $proxy_host variable, and “Connection” is set to close.

HTTPS

http {
# reuse SSL session parameters to avoid SSL handshakes for parallel and subsequent connections.
# or "ssl_session_cache builtin:1000 shared:SSL:10m;"
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# enabling keepalive connections to send several requests via one connection and the second is to reuse SSL session parameters to avoid SSL handshakes for parallel and subsequent connections.
keepalive_timeout 70;

server {
listen 443 ssl;
server_name myproject.com;

ssl_certificate /etc/ssl/projectName/projectName.com.pem;
ssl_certificate_key /etc/ssl/projectName/projectName.com.key;

# Additional SSL configuration (if required)
ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
ssl_prefer_server_ciphers on;

# same with HTTP
location / {
...
}
}
...
}

HTTP to HTTPS

http {
...
server {
listen 80;
server_name myserver.com;
return 301 https://myserver.com$request_uri;
}
...
}

Optimization

Caching

Cache static files

When you build static assets with versioning/hashing mechanisms, adding a version/hash to the filename (cache-busting filenames) or query string is a good way to manage caching. In such a case, you can add a long max-age value and immutable because the content will never change.

http {
server {
...
location /static {
root /var/www/your_domain/staic;
# To disable access log off for saving disk I/O
access_log off;
# or "expires max";
expires 7d;
# build static assets with versioning/hashing mechanisms
add_header Cache-Control "public, immutable";
# revalidate
# add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
...
}
}
http {
server {
...
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2|woff|ttf|eot|pdf|txt)$ {
root /var/www/your_domain/staic;
# To disable access log off for not hitting the I/O limit
access_log off;
# or "expires max";
expires 7d;
# build static assets with versioning/hashing mechanisms
add_header Cache-Control "public, immutable";
# revalidate
# add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
...
}
}

After setting cache, the following headers will be present in the response headers:

Cache-Control: max-age=604800, public, must-revalidate, proxy-revalidate

Warning: Cache-Control "public, immutable" for JS, CSS may cause program exceptions after update program.

Compression

Enable gzip

http {
...
server {
...
# gzip can be set in `http {}` or `server {}`
# --with-http_gzip_static_module
gzip on;
# By default, NGINX compresses responses only with MIME type text/html. To compress responses with other MIME types, include the gzip_types directive and list the additional types.
gzip_types text/css application/json text/xml application/javascript;
# To specify the minimum length of the response to compress, use the gzip_min_length directive. The default is 20 bytes
gzip_min_length 200;
# Sets the number and size of buffers used to compress a response.
gzip_buffers 32 4k;
# Sets a gzip compression level of a response. Acceptable values are in the range from 1 to 9.
gzip_comp_level 6;
gzip_vary on;
...
}
}

After enable gzip, the following headers will be present in the response headers:

content-encoding: gzip

Enable HTTP/2

The ngx_http_v2_module module (1.9.5) provides support for HTTP/2. This module is not built by default, it should be enabled with the --with-http_v2_module configuration parameter.

To enable HTTP/2, you must first enable SSL/TLS on your website. HTTP/2 requires the use of SSL/TLS encryption, which provides a secure connection between the web server and the client’s browser.

http {
server {
# enable http2
listen 443 ssl http2;

ssl_certificate server.crt;
ssl_certificate_key server.key;
}
}

Connection Handling

Keepalive Connections

http {
keepalive_timeout 65;
}

Keepalive connections reduce the overhead of repeatedly establishing new connections for multiple requests from the same client (e.g., a web browser loading multiple assets from a single page). This saves time and resources associated with TCP handshake and SSL/TLS negotiation.

TCP Optimizations

http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}
  • sendfile → efficient file transfer.
  • tcp_nopush → optimizes packet sending.
  • tcp_nodelay → reduces latency for small packets.

When sendfile on; and tcp_nopush on; are used together in Nginx for serving static files, Nginx will initially buffer data to send full TCP packets.

However, for the very last packet(s) of a file, which may not be full, Nginx will dynamically disable tcp_nopelay (effectively removing TCP_CORK) and enable tcp_nodelay to ensure that these remaining partial packets are sent immediately without delay, thus completing the file transfer quickly.

SSL/TLS Optimization

http {
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;

ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
}
  • ssl_session_cache and ssl_session_timeout: Reuse SSL sessions → fewer handshakes.
  • ssl_protocols: TLS 1.3 is faster than TLS 1.2.

Worker Processes and Connections

http {
worker_processes auto;
}
  • worker_processes: This directive determines the number of Nginx worker processes that will handle incoming requests. A common practice is to set this to auto, which automatically sets the number of worker processes to match the number of CPU cores on your server. This ensures that each core is utilized efficiently.

Logging

Disabling Access Logs

While access logs are valuable for monitoring and debugging, they can consume significant CPU and disk resources on high-traffic sites. If you don’t need detailed logging for every request, you can either buffer the logs or disable them entirely to reduce overhead.

To disable access log for static files

http {
server {
...
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2|woff|ttf|eot|pdf|txt)$ {
# To disable access log off for saving disk I/O
access_log off;
}
...
}
}

Or disable all access logs

http {
# To disable access log off for saving disk I/O
access_log off;
}

Buffering Logs (Optional)

Instead of writing to the log file for every single request, you can configure Nginx to buffer log data and write it in larger chunks. This reduces the number of disk I/O operations and can improve performance.

Benefits of Log Buffering:

  • Reduced I/O Operations: Fewer, larger writes to disk instead of many small writes, improving performance.
  • Lower CPU Consumption: Less overhead associated with managing individual log entries.
  • Improved Disk Lifespan: Reduced wear and tear on storage devices.

Considerations:

  • Data Latency: Buffered log entries are not immediately written to disk, which can introduce a slight delay in log availability for real-time analysis.
  • Memory Usage: Buffering consumes a small amount of memory per worker process.
  • Out-of-Order Entries: In rare cases, if multiple worker processes are writing to the same log file with buffering enabled, log entries might appear slightly out of order if not flushed simultaneously. However, most log analysis systems can handle this by sorting based on timestamps.

Settings

Timeout

http {
...
# proxy_connect_timeout default 60s
proxy_connect_timeout 180s;
# proxy_send_timeout default 60s
proxy_send_timeout 180s;
# proxy_read_timeout default 60s
proxy_read_timeout 180s;
...
}

Upload File Size

http {
...
# client_max_body_size default 1M
client_max_body_size 100M;
...
}

Enable CORS for API

Enable CORS for specified sites

http {
...

map "$http_origin" $cors {
default '';
"~^https?://localhost(:\d+)?$" "$http_origin";
"~^https?://10.0.0\.\d{1,2}(:\d+)?$" "$http_origin";
"~^https?://example\.com$" "$http_origin";
}

server {
# API
location /api/ {
...

# Attach CORS headers only if it's a valid origin ($cors should not be empty)
if ($cors != "") {
# using $cors for specified sites or using $http_origin for any sites.
proxy_hide_header 'Access-Control-Allow-Origin';
add_header 'Access-Control-Allow-Origin' '$cors' always;
proxy_hide_header 'Access-Control-Allow-Credentials';
add_header 'Access-Control-Allow-Credentials' true always;
proxy_hide_header 'Access-Control-Allow-Methods';
add_header 'Access-Control-Allow-Methods' 'POST, GET, DELETE, PUT, PATCH' always;
proxy_hide_header 'Access-Control-Allow-Headers';
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
}
# Check if it's a preflight request and "cache" it for 20 days
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$cors' always;
add_header 'Access-Control-Allow-Credentials' true always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}

...
}
}
}

Enable CORS for all sites

http {
...

server {
# API
location /api/ {
...

proxy_hide_header 'Access-Control-Allow-Origin';
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
proxy_hide_header 'Access-Control-Allow-Credentials';
add_header 'Access-Control-Allow-Credentials' true always;
proxy_hide_header 'Access-Control-Allow-Methods';
add_header 'Access-Control-Allow-Methods' 'POST, GET, DELETE, PUT, PATCH' always;
proxy_hide_header 'Access-Control-Allow-Headers';
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;

# Check if it's a preflight request and "cache" it for 20 days
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Credentials' true always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}

...
}
}
}

Load Balancing

http {
...
upstream backend-server {
server xxx.xxx.xxx.xxx:8080 max_fails=1 fail_timeout=300s;
server xxx.xxx.xxx.xxx:8080 max_fails=1 fail_timeout=300s;
...
}

server {
...
location /api/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend-server/;
}
}
}

Test the Nginx Configuration is Updated

Adding the following config to the Nginx configuration file. You can verify if the configuration is updated by updating the return status code (e.g. 403 Forbidden, 406 Not Acceptable, 423 Locked) of the /test location and visiting the test URL http://yourDomain/testConfig.

location /testConfig {
# 403 Forbidden, 406 Not Acceptable, 423 Locked
return 403;
}

Appendixes

Embedded Variables

  • $proxy_host: name and port of a proxied server as specified in the proxy_pass directive;
  • $proxy_add_x_forwarded_for: the “X-Forwarded-For” client request header field with the $remote_addr variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.
  • $host: In this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request.
  • $remote_addr: Client address

Build Nginx From Source

# Download Nginx source code
wget http://nginx.org/download/nginx-{latest-stable-version}.tar.gz

You can know the latest version of Nginx by visiting the Nginx download page.

tar -zxvf nginx-{latest-stable-version}.tar.gz

cd nginx-{latest-stable-version}

# Configuring Nginx
./configure \
--with-pcre \
--with-http_ssl_module \
--with-http_image_filter_module=dynamic \
--modules-path=/etc/nginx/modules \
--with-http_v2_module \
--with-stream=dynamic \
--with-http_addition_module \
--with-http_mp4_module \
--with-http_gzip_static_module

More configuration

Common errors when running ./configure

1. ./configure: error: the HTTP rewrite module requires the PCRE library.

Solution

sudo apt update && apt upgrade -y && apt autoremove && apt autoclean
apt-get install libpcre3 libpcre3-dev
# or
sudo yum update -y && yum upgrade && yum autoremove
yum install libpcre3 libpcre3-dev
yum -y install pcre-devel openssl openssl-devel

2. ./configure: error: the HTTP image filter module requires the GD library. You can either do not enable the module or install the libraries.

Solution

sudo apt update && apt upgrade -y && apt autoremove && apt autoclean
apt-get install gd-devel
# or
sudo yum update -y && yum upgrade && yum autoremove
yum install gd-devel -y

3. ./configure: error: C compiler cc is not found

Solution

sudo apt update && apt upgrade -y && apt autoremove && apt autoclean
sudo apt-get install linux-kernel-headers build-essential -y
# or
sudo yum update -y && yum upgrade && yum autoremove
sudo yum install gcc -y

Successful output of configure

Configuration summary
+ using system PCRE2 library
+ using system OpenSSL library
+ using system zlib library

nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/nginx/sbin/nginx"
nginx modules path: "/etc/nginx/modules"
nginx configuration prefix: "/usr/local/nginx/conf"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
nginx pid file: "/usr/local/nginx/logs/nginx.pid"
nginx error log file: "/usr/local/nginx/logs/error.log"
nginx http access log file: "/usr/local/nginx/logs/access.log"
nginx http client request body temporary files: "client_body_temp"
nginx http proxy temporary files: "proxy_temp"
nginx http fastcgi temporary files: "fastcgi_temp"
nginx http uwsgi temporary files: "uwsgi_temp"
nginx http scgi temporary files: "scgi_temp"

You can add the following parameters to specify paths:

--prefix=/var/www/html \
--sbin-path=/usr/sbin/nginx \
--modules-path=/etc/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--lock-path=/var/lock/nginx.lock \
# Build nginx
$ make
$ sudo make install

Successful output of build

make -f objs/Makefile install
make[1]: 进入目录“/root/nginx-1.26.1”
...
# make[1]: 离开目录“/root/nginx-1.26.1”
# Start nginx
$ cd /usr/local/nginx/sbin
$ ./nginx -V
$ ./nginx
# Verify
$ curl http://localhost

Rebuild Nginx source

# Just remove the nginx binary file. Or completely remove nginx `sudo apt-get purge nginx` or `yum remove package`
cd /usr/local/nginx/sbin
mv nginx nginx.bak

# configure
tar -zxvf nginx-{latest-stable-version}.tar.gz
cd nginx-{latest-stable-version}
# Configuring Nginx
./configure ...

# Build and install nginx
$ make
$ sudo make install

References

[1] Configuring HTTPS servers - Nginx

[2] Alphabetical index of variables - Nginx

[3] Serving Static Content - Nginx

[4] NGINX Reverse Proxy - Nginx