Secure nginx reverse proxy with automated threat blocking, GeoIP filtering, and production-ready configs
Find a file
2026-06-24 06:01:15 +00:00
.forgejo/workflows fixed absolute path for modules 2026-01-28 05:04:47 +01:00
artifacts chore: update smart blocklist [skip ci] 2026-06-24 06:01:15 +00:00
config fixed absolute path for modules 2026-01-28 05:04:47 +01:00
template restructured configuration locations 2026-01-28 01:58:45 +01:00
.gitignore restructured configuration locations 2026-01-28 01:58:45 +01:00
docker-compose.yml restructured configuration locations 2026-01-28 01:58:45 +01:00
Dockerfile fixed absolute path for modules 2026-01-28 05:04:47 +01:00
LICENSE Initial commit 2025-12-24 16:07:33 +01:00
README.md added missing features list 2026-01-13 22:24:23 +01:00
update.sh added auto-update 2026-01-13 22:22:13 +01:00

 ▐ ▄  ▄▄ • ▪   ▐ ▄ ▐▄• ▄ ▄▄▄   ▄▄▄·
•█▌▐█▐█ ▀ ▪██ •█▌▐█ █▌█▌▪▀▄ █·▐█ ▄█
▐█▐▐▌▄█ ▀█▄▐█·▐█▐▐▌ ·██· ▐▀▀▄  ██▀·
██▐█▌▐█▄▪▐█▐█▌██▐█▌▪▐█·█▌▐█•█▌▐█▪·•
▀▀ █▪·▀▀▀▀ ▀▀▀▀▀ █▪•▀▀ ▀▀.▀  ▀.▀

nginx-reverse-proxy

Production-ready nginx reverse proxy with automated SSL, threat blocking, and GeoIP filtering.

Features

  • Automatic SSL/TLS - ACME module for Let's Encrypt (no certbot) with HSTS
  • Rate Limiting - Default and login-specific rate limiting for DDoS and brute force protection
  • Custom Error Pages - Professional error pages with lost-in-space theme
  • GeoIP2 Intelligence - Location-based filtering and enriched logging
  • Threat Protection - Automated blocklist aggregation from multiple threat intelligence sources
  • HTTP/2 + TLSv1.3 - Modern protocol support based on Mozilla SSL Configuration
  • WebSocket Ready - Full upgrade support for real-time applications
  • Security Headers - Browser protection headers (HSTS, X-Frame-Options, etc.)
  • Fail2ban Integration - Automated IP banning for repeated violations
  • Rickroll Default Server - Catch-all for undefined domains

Prerequisites

  • Docker and Docker Compose
  • Valid email address for ACME/Let's Encrypt notifications (configured in config/nginx.conf)

ACME Email Configuration:

Edit config/nginx.conf and update the contact email in the ACME issuer block:

acme_issuer letsencrypt {
    uri https://acme-v02.api.letsencrypt.org/directory;
    contact your-email@example.com;  # Change this
    state_path /var/cache/nginx/acme;
    accept_terms_of_service;
}

This email receives certificate expiration warnings and ACME account notifications from Let's Encrypt.

Quick Start

git clone https://quelloffen.ch/homelab/nginx-reverse-proxy
cd nginx-reverse-proxy

Configure local-only access (optional):

  1. Edit config/features/local-only.conf and add your public IP:
allow YOUR_PUBLIC_IP/32;
  1. Edit config/nginx.conf in the geo $is_trusted_ip block and add your public IP:
YOUR_PUBLIC_IP/32 1;

Start:

docker compose up -d

Update configuration:

git stash               # Temporarily hide your changes
git pull                # Get updates
git stash pop           # Reapply your changes

Reload after config changes:

docker exec nginx nginx -t && docker exec nginx nginx -s reload

Adding a New Site

1. Copy template:

cp config/template/domain.tld.conf config/conf.d/yourdomain.com.conf

2. Edit these values:

  • SERVICE_NAME-DOMAIN → unique identifier
  • IP:PORT → backend server address
  • FQDN → your domain name

3. Test and reload:

docker exec nginx nginx -t && docker exec nginx nginx -s reload

Maintenance

Automated Updates

Automatically pull configuration updates from the repository and reload nginx daily at 7 AM using a cron job.

Setup:

  1. Install cron (if not already installed):
sudo apt install cron
sudo systemctl enable --now cron
  1. Add the update script to your crontab:
crontab -e

Add this line:

0 7 * * * /opt/nginx-reverse-proxy/update.sh

Update Script Behavior:

  • Stashes local changes (your custom configurations)
  • Pulls latest updates from repository
  • Restores your local changes
  • Tests nginx configuration validity
  • Reloads nginx if tests pass
  • Logs all operations to logs/auto-update.log

If configuration test fails, the script exits without reloading nginx, preventing broken configurations from being applied.

Customizing Error Pages

Extract and modify the error pages from the tarampampam/error-pages container to customize the lost-in-space theme.

Extract error pages:

docker run -d --name error-pages-tmp ghcr.io/tarampampam/error-pages:3 serve
docker cp error-pages-tmp:/opt/html/lost-in-space/. /tmp/error-pages/
docker rm -f error-pages-tmp

The extracted HTML files can then be modified and placed in config/error-pages/ directory. Edit HTML/CSS directly to match your branding or design preferences.

Fail2ban Integration

Automated intrusion prevention system that bans IPs showing malicious behavior (repeated rate limit violations). Integrates with nginx logs to detect and block attackers at the firewall level using iptables.

How it works:

  • Monitors nginx error logs for rate limit violations
  • Bans IPs after 3 violations within 90 days
  • Uses incremental ban times: 10min → 1day → 1year
  • Automatically adds banned IPs to iptables FORWARD chain
  • Respects trusted networks (RFC1918 + localhost)

Configuration files:

  • fail2ban/config/jail.d/jail.conf - Jail settings and ban policy
  • fail2ban/config/filter.d/nginx-limit-req.conf - Log pattern matching

View banned IPs:

docker exec fail2ban fail2ban-client status nginx-limit-req

Manually unban an IP:

docker exec fail2ban fail2ban-client set nginx-limit-req unbanip <IP>

Fail2ban runs in host network mode to directly manage iptables rules. Banned IPs are persisted in data/fail2ban/ across container restarts.

Fail2ban Documentation

Features

Core Configuration (nginx.conf)

Central configuration file defining global settings, loaded modules, rate limiting zones, GeoIP2 mappings, logging format, and error page directives.

Key Components:

  • ACME issuer configuration for Let's Encrypt
  • SSL/TLS protocols (TLSv1.3 only)
  • GeoIP2 database loading (Country, City, ASN)
  • Rate limiting zones (normal_limit, login_limit)
  • Trusted IP mappings (geo $is_trusted_ip)
  • Blocklist inclusion
  • Custom error page mappings

HTTP Core Module Documentation | Main Context Documentation

Rickroll Default Server (conf.d/default.conf)

Catch-all default server that handles requests for undefined domains or direct IP access. Redirects all HTTP traffic to a rickroll and rejects HTTPS handshakes, preventing information disclosure about hosted services.

HTTP Behavior (Port 80):

location / {
    return 302 https://www.youtube.com/watch?v=dQw4w9WgXcQ;
}

All requests without a matching server_name are redirected to Rick Astley's "Never Gonna Give You Up".

HTTPS Behavior (Port 443):

ssl_reject_handshake on;

Rejects SSL/TLS handshakes for undefined domains, preventing certificate enumeration and protecting against SNI-based reconnaissance. Attackers scanning your IP won't see which domains are hosted.

The default server includes threat protection and GeoIP blocking but disables logging to reduce noise from scanner traffic.

Server Names Documentation | SSL Configuration

Security Headers (features/security-headers.conf)

Browser security headers that protect against common web vulnerabilities. Applied to all proxied locations in your site configurations.

Headers configured:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Protection provided:

  • HSTS - Forces HTTPS for 1 year, includes subdomains
  • X-Frame-Options - Prevents clickjacking by restricting iframe embedding
  • X-Content-Type-Options - Stops MIME type sniffing attacks
  • Referrer-Policy - Controls referrer information leakage to external sites

These headers are automatically applied when you include security-headers.conf in your location blocks (already included in the site template).

Headers Module Documentation | OWASP Secure Headers

Site Configuration Template (template/domain.tld.conf)

Template for adding new sites with recommended security features. Includes upstream definition, HTTP to HTTPS redirect, and HTTPS server block with rate limiting, HSTS headers, and feature includes.

Usage: Copy template and replace SERVICE_NAME-DOMAIN, IP:PORT, and FQDN placeholders.

Server Block Documentation | Upstream Module Documentation

ACME SSL (acme-ssl.conf)

Automatic Let's Encrypt certificates with native ACME protocol support. Certificates are requested and renewed automatically without external tools like certbot. Includes HSTS header (HTTP Strict Transport Security) for enhanced security by enforcing HTTPS connections.

HSTS Configuration:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

ACME Module Documentation | ACME Module Source | HSTS Header Documentation

Default Rate Limiting (default-rate-limit.conf)

Global rate limiting applied to all endpoints to prevent DDoS and brute force attacks. Limits processing to 100 requests/second per IP with a burst of 200 requests (allows temporary traffic spikes). The nodelay parameter processes burst requests immediately without queueing.

Zone Configuration (nginx.conf):

limit_req_zone $untrusted_ip zone=normal_limit:10m rate=100r/s;

Trusted IPs (local networks and public IPs configured in nginx.conf geo $is_trusted_ip block) are automatically exempted from rate limiting.

Limit Request Module Documentation

Login Rate Limiting (login-rate-limit.conf)

Stricter rate limiting for authentication endpoints (login, auth, signin, API auth paths). Limits processing to 5 requests/second per IP with a burst of 10 requests to prevent credential stuffing attacks. The nodelay parameter processes burst requests immediately.

Zone Configuration (nginx.conf):

limit_req_zone $untrusted_ip zone=login_limit:10m rate=5r/s;

Apply to specific authentication locations in your site configuration.

Limit Request Module Documentation

Custom Error Pages (error-pages.conf)

Professional error pages using the lost-in-space theme from tarampampam/error-pages. Provides user-friendly error messages for common HTTP status codes (400, 401, 403, 404, 429, 500, 502, 503, 504).

Configuration:

  • proxy_intercept_errors on - Captures backend error responses
  • Internal /error-pages/ location serves static HTML pages
  • Error page mappings defined in nginx.conf with error_page directives

Error Page Directive Documentation | Error Pages Project

Threat Protection (block-by-ip-lists.conf)

Automated blocklist aggregation from multiple threat intelligence sources, updated daily via CI/CD. Check the update-blocklist script for current sources.

Processing:

  • Combined from all sources
  • Validated and deduplicated with mapcidr
  • CIDR ranges merged and optimized
  • Automatically committed to repository daily

Custom Blocklist:

Add your own IPs to config/blocklist/custom-blocklist.txt:

1.2.3.4 1;
2.3.4.5/24 1;
3.4.5.6/32 1;

Format: IP_ADDRESS 1; (one per line, semicolon required)

Geographic Blocking (geoblock-if_not_in_EU.conf)

Restricts traffic to Europe only using MaxMind GeoIP2 continent detection. Blocks all non-EU traffic before reaching your application. GeoIP2 databases are updated weekly.

GeoIP2 Module Documentation | GeoIP2 Module Source | MaxMind GeoLite2

Local-Only Access (local-only.conf)

Restricts access to trusted networks only using nginx's allow and deny directives. Useful for admin panels, internal tools, or sensitive endpoints. Includes RFC1918 private networks and localhost by default.

Configuration:

allow 127.0.0.1;
allow ::1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
allow YOUR_PUBLIC_IP/32;
deny all;

Connections from untrusted networks receive a 403 Forbidden response.

Setup: Add your public IP to config/features/local-only.conf with the allow directive and semicolon. Additionally, add your public IP to the geo $is_trusted_ip block in config/nginx.conf to exempt it from rate limiting.

Access Module Documentation | Geo Module Documentation

Proxy Headers (proxy-headers.conf)

Standard reverse proxy headers required for backend applications to receive client information:

  • X-Real-IP - Client's actual IP address
  • X-Forwarded-For - Full proxy chain
  • X-Forwarded-Proto - Original protocol (http/https)
  • X-Forwarded-Host - Original host header
  • Host - Preserves original host

Required for all proxied locations.

Proxy Module Documentation

WebSocket Support (websocket.conf)

Enables WebSocket protocol upgrades for real-time applications (Nextcloud, n8n, etc.). Handles HTTP/1.1 Upgrade headers and Connection header mapping configured in nginx.conf.

Required for: Real-time applications, chat systems, live notifications, collaborative tools.

WebSocket Proxying Documentation

Configuration

Required Features

include /etc/nginx/features/acme-ssl.conf;         # Automatic SSL with HSTS
include /etc/nginx/features/proxy-headers.conf;    # Backend communication
include /etc/nginx/features/security-headers.conf; # Browser security headers
include /etc/nginx/features/error-pages.conf;      # Custom error pages
include /etc/nginx/features/default-rate-limit.conf;    # Global rate limiting
include /etc/nginx/features/block-by-ip-lists.conf;    # Threat protection
include /etc/nginx/features/geoblock-if_not_in_EU.conf; # Geographic filtering

Authentication Endpoints (login, auth paths)

include /etc/nginx/features/login-rate-limit.conf;     # Stricter rate limiting

Note: While not a full WAF (Web Application Firewall), combining rate limiting, IP blocklists, and geographic filtering significantly reduces attack surface and unwanted traffic. These features prevent the majority of automated attacks, bot traffic, and malicious requests before they reach your applications.

Conditional Features

include /etc/nginx/features/local-only.conf;    # Restrict to trusted networks
include /etc/nginx/features/websocket.conf;     # Enable WebSocket support

Documentation