Global HTTP Settings

Posted on 18 2026

The global HTTP settings live in /etc/nginx/conf.d/. Every file in this directory is included by the http {} block in nginx.conf and applies to all virtual servers unless explicitly overridden. The source material uses numbered files in a custom http-conf.d/ directory. This series uses the standard conf.d/ directory with descriptive filenames.

TCP optimisation

Create /etc/nginx/conf.d/tcp.conf:

#
# TCP/IP network optimisation
# /etc/nginx/conf.d/tcp.conf
#

# Use sendfile() for efficient file transfer.
# Default: off
sendfile on;

# Send response headers and file content in one packet.
# Only effective when sendfile is enabled.
# Default: off
tcp_nopush on;

# Send small packets immediately without buffering.
# Most useful for keepalive connections.
# Default: on
tcp_nodelay on;

# Reset timed-out connections, freeing RAM.
# Default: off
reset_timedout_connection on;

TLS global settings

Create /etc/nginx/conf.d/ssl.conf:

#
# Global TLS/SSL configuration
# /etc/nginx/conf.d/ssl.conf
# Applied to all HTTPS virtual servers
#

# TLS protocols: TLS 1.2 and 1.3 only
# TLS 1.0 and 1.1 are deprecated (RFC 8996)
ssl_protocols TLSv1.2 TLSv1.3;

# Cipher suite for TLS 1.2
# TLS 1.3 cipher suites are not configurable in nginx
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';

# Prefer server cipher order for TLS 1.2
# Not applicable to TLS 1.3 (client preference is used)
ssl_prefer_server_ciphers on;

# ECDH curve for ephemeral key exchange
ssl_ecdh_curve X25519:secp384r1:prime256v1;

# Diffie-Hellman parameters for DHE cipher suites
# Generate: openssl dhparam -out /etc/nginx/dhparam.pem 4096
# DHE is rarely used with modern cipher suites but included for compatibility
ssl_dhparam /etc/nginx/dhparam.pem;

# TLS session cache: shared across all worker processes
# 1m cache stores approximately 4000 sessions
ssl_session_cache shared:SSL:10m;

# Time during which a client may reuse the cached session
ssl_session_timeout 1d;

# Disable TLS session tickets for forward secrecy
# Session tickets require careful key rotation; disabling is simpler and safer
ssl_session_tickets off;

# OCSP stapling: attach the certificate revocation status to the TLS handshake
ssl_stapling on;
ssl_stapling_verify on;

# Resolver for OCSP stapling (internal Unbound resolver)
resolver 10.1.0.x valid=300s;
resolver_timeout 5s;

Generating DH parameters

sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

This takes several minutes. Run it once after installation.

Timeouts and buffer limits

Create /etc/nginx/conf.d/limits.conf:

#
# Connection timeouts and buffer limits
# /etc/nginx/conf.d/limits.conf
#

# Maximum time between packets the client can pause when sending data
# Default: 60s
client_body_timeout 10s;

# Maximum time the client has to send the entire request header
# Default: 60s
client_header_timeout 10s;

# Time a keepalive connection stays open on the server side
# Default: 75s
keepalive_timeout 75s;

# Timeout for sending the response to the client
# Default: 60s
send_timeout 30s;

# Buffer size for reading client request body
# Default: 16k
client_body_buffer_size 128k;

# Buffer size for reading client request headers
# Default: 1k
client_header_buffer_size 3k;

# Maximum size for large client request headers
# Default: 4 8k
large_client_header_buffers 4 16k;

# Maximum allowed client request body size
# Set per virtual server for upload-capable services (Nextcloud needs a high value)
# Default: 1m
client_max_body_size 10m;

Rate limiting

Create /etc/nginx/conf.d/rate-limiting.conf:

#
# Rate limiting zones
# /etc/nginx/conf.d/rate-limiting.conf
# Zones defined here; limits applied per virtual server or location
#

# Zone for per-IP request rate limiting
# 10m zone stores ~160,000 IP addresses
# Adjust rate per virtual server as needed
limit_req_zone $binary_remote_addr zone=ip_req:10m rate=10r/s;

# Zone for per-server request rate limiting
limit_req_zone $server_name zone=server_req:10m rate=100r/s;

# Zone for per-IP connection limiting
limit_conn_zone $binary_remote_addr zone=ip_conn:10m;

# Return 429 (Too Many Requests) rather than 503 for rate-limited requests
limit_req_status 429;
limit_conn_status 429;

# Log rate limit violations at warn level
limit_req_log_level warn;
limit_conn_log_level warn;

Rate limits are defined as zones here but applied in individual virtual server and location blocks. A general rate limit applied globally would be too restrictive for some services (Nextcloud sync can make many requests per second) and too permissive for others (login endpoints should be tightly limited).

Security settings

Create /etc/nginx/conf.d/security.conf:

#
# Global HTTP security settings
# /etc/nginx/conf.d/security.conf
#

# Do not send nginx version in Server header or error pages
# Default: on
server_tokens off;

# Disable directory listing
# Default: off (already off, stated explicitly)
autoindex off;

# Do not allow content type sniffing
# Applied as a header: X-Content-Type-Options: nosniff
# (Set per virtual server in security headers; noted here for reference)

# Reject requests with invalid header names
# Default: on
ignore_invalid_headers on;

# Reject header names containing underscores (common in attack tools)
# Default: off
underscores_in_headers off;

The source material sets a fake Server: header using the more_set_headers module. This requires the nginx-extras package and is security theatre rather than genuine protection: it does not prevent fingerprinting and adds maintenance overhead. server_tokens off removes the version number which is the meaningful information to withhold.

Logging

Create /etc/nginx/conf.d/logging.conf:

#
# Logging format and defaults
# /etc/nginx/conf.d/logging.conf
#

# Standard combined log format with real IP and forwarded-for header
log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent" '
                '"$http_x_forwarded_for" "$host"';

# Extended format with timing information (useful for performance analysis)
log_format timing '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  'rt=$request_time uct="$upstream_connect_time" '
                  'uht="$upstream_header_time" urt="$upstream_response_time"';

# Disable access logging globally by default
# Enable per virtual server with:
#   access_log /var/log/nginx/sitename.access.log main;
access_log off;

# Do not log 404 errors for missing files
log_not_found off;

# Global error log (set in nginx.conf; restated here as reference)
# error_log /var/log/nginx/error.log warn;

The source material turns access logging off globally. This is a reasonable default for a homelab: the log volume from automated scanners is significant and per-site logs are more useful than a combined access log. Individual virtual server configurations enable logging for each service.

Compression

Create /etc/nginx/conf.d/gzip.conf:

#
# Gzip compression
# /etc/nginx/conf.d/gzip.conf
#

# Enable gzip compression
gzip on;

# Compression level 5: good ratio with reasonable CPU cost
# Default: 1
gzip_comp_level 5;

# Compress responses for proxied requests
# Default: off
gzip_proxied any;

# Add Vary: Accept-Encoding header so proxies cache both versions
gzip_vary on;

# Minimum response size to compress (avoid compressing tiny responses)
gzip_min_length 256;

# Compress these MIME types (text/html is always compressed)
gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/rss+xml
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/svg+xml
    image/x-icon
    text/css
    text/plain
    text/x-component;

The source material also includes Brotli compression. Brotli achieves better compression ratios than gzip, particularly for text content. However, the ngx_brotli module is not included in the standard nginx package. It requires either nginx-extras from the Ubuntu repository or a source build.

If nginx-extras is installed:

# Add to gzip.conf if nginx-extras is installed and brotli module is present
# Verify with: nginx -V 2>&1 | grep brotli

brotli on;
brotli_comp_level 6;
brotli_types
    application/atom+xml
    application/javascript
    application/json
    application/rss+xml
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/xhtml+xml
    application/xml
    font/opentype
    image/svg+xml
    image/x-icon
    text/css
    text/plain
    text/x-component;

Open files cache

Create /etc/nginx/conf.d/open-file-cache.conf:

#
# Open files cache
# /etc/nginx/conf.d/open-file-cache.conf
# Caches file descriptors, reducing disk operations for static file serving
#

# Cache up to 10000 file descriptors; remove unused after 30 minutes
open_file_cache max=10000 inactive=30m;

# Cache file lookup errors (404, permission denied, etc.)
open_file_cache_errors on;

# Revalidate cached file metadata every 30 minutes
open_file_cache_valid 30m;

# File must be accessed at least once to remain cached
open_file_cache_min_uses 1;

PHP FastCGI upstream

Create /etc/nginx/conf.d/php-fpm.conf:

#
# PHP-FPM upstream definition
# /etc/nginx/conf.d/php-fpm.conf
# Referenced by virtual servers that need PHP processing
#

upstream php-fpm {
    server unix:/var/run/php/php8.3-fpm.sock;
}

# FastCGI buffer settings
# Default: 8 8k
fastcgi_buffers 128 8k;

# Buffer for first part of FastCGI response
fastcgi_buffer_size 32k;

The source material references PHP 7.0 which reached end-of-life in December 2019. Ubuntu 24.04 ships PHP 8.3.

FastCGI cache

A FastCGI cache stores rendered PHP responses and serves them without re-executing PHP for repeat requests. Useful for Nextcloud thumbnails and similar cacheable content.

Create /etc/nginx/conf.d/fastcgi-cache.conf:

#
# FastCGI cache zone definitions
# /etc/nginx/conf.d/fastcgi-cache.conf
# Zones defined here; cache applied per virtual server location
#

# Cache for Nextcloud (thumbnails and gallery)
fastcgi_cache_path
    /var/cache/nginx/nextcloud
    levels=1:2
    keys_zone=NEXTCLOUD:100m
    inactive=60m
    max_size=1g;

# Create the cache directory:
# sudo mkdir -p /var/cache/nginx/nextcloud
# sudo chown www-data:www-data /var/cache/nginx/nextcloud

The source material includes cache zones for WordPress, ownCloud, and Wallabag. This series uses Nextcloud rather than ownCloud and does not run WordPress. Add additional zones when other PHP services are deployed.

Default catch-all server

The default catch-all server handles connections with no matching virtual server: IP address probing, unknown hostnames, and automated scanners. It returns 444 (no response) which closes the connection immediately.

Create /etc/nginx/sites-available/default:

#
# Default catch-all server
# /etc/nginx/sites-available/default
# Handles connections that do not match any named virtual server
#

# HTTP catch-all
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # Allow ACME challenges for certificate renewal
    location /.well-known/acme-challenge/ {
        root /var/www/acme-challenge;
        allow all;
    }

    # Redirect everything else to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS catch-all
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    http2 on;

    # Self-signed certificate for the default server
    # Legitimate clients never reach this server block
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    # Return no response and close the connection
    return 444;

    access_log /var/log/nginx/default.access.log main;
}

The Ubuntu package provides a self-signed snake oil certificate for exactly this purpose. It is fine to use here because no legitimate client should ever negotiate TLS with the catch-all server: legitimate connections match a named virtual server first.

Activate the default server:

sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Verifying the configuration

After creating all the conf.d/ files, validate the full configuration:

sudo nginx -T 2>/dev/null | head -100

nginx -T dumps the full resolved configuration including all includes. Scan it to confirm all conf.d/ files are being loaded and the settings appear as expected.

sudo nginx -t
sudo systemctl reload nginx

keepalive_timeout in the source material is set to 1d 1d (one day). This is extremely long for a public-facing web server and ties up file descriptors and memory for idle connections. The 75-second default is appropriate for most cases. Increase it per virtual server only if a specific backend genuinely benefits from long keepalive connections.