- HTML 99.9%
| .forgejo/workflows | ||
| artifacts | ||
| config | ||
| template | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| update.sh | ||
▐ ▄ ▄▄ • ▪ ▐ ▄ ▐▄• ▄ ▄▄▄ ▄▄▄·
•█▌▐█▐█ ▀ ▪██ •█▌▐█ █▌█▌▪▀▄ █·▐█ ▄█
▐█▐▐▌▄█ ▀█▄▐█·▐█▐▐▌ ·██· ▐▀▀▄ ██▀·
██▐█▌▐█▄▪▐█▐█▌██▐█▌▪▐█·█▌▐█•█▌▐█▪·•
▀▀ █▪·▀▀▀▀ ▀▀▀▀▀ █▪•▀▀ ▀▀.▀ ▀.▀
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):
- Edit
config/features/local-only.confand add your public IP:
allow YOUR_PUBLIC_IP/32;
- Edit
config/nginx.confin thegeo $is_trusted_ipblock 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 identifierIP:PORT→ backend server addressFQDN→ 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:
- Install cron (if not already installed):
sudo apt install cron
sudo systemctl enable --now cron
- 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 policyfail2ban/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.
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_pagedirectives
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 addressX-Forwarded-For- Full proxy chainX-Forwarded-Proto- Original protocol (http/https)X-Forwarded-Host- Original host headerHost- Preserves original host
Required for all proxied locations.
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
Optional but Recommended for Public Services
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