bastebin/nginx.conf.example

105 lines
4.1 KiB
Plaintext

# ── Bastebin — Nginx reverse-proxy configuration ─────────────────────────────
#
# Copy to /etc/nginx/sites-available/bastebin and symlink:
# sudo ln -s /etc/nginx/sites-available/bastebin /etc/nginx/sites-enabled/
# sudo nginx -t && sudo systemctl reload nginx
#
# Assumes:
# - Gunicorn is bound to a Unix socket at /run/bastebin/gunicorn.sock
# - Your TLS certificates are managed by Certbot / Let's Encrypt
# - Static files live at /path/to/bastebin/static/
#
# Replace every occurrence of:
# bastebin.com → your actual domain
# /path/to/bastebin → absolute path to your project directory
server {
listen 80;
listen [::]:80;
server_name bastebin.com www.bastebin.com;
# Redirect all HTTP → HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bastebin.com www.bastebin.com;
# ── TLS (managed by Certbot — do not edit manually) ──────────────────────
ssl_certificate /etc/letsencrypt/live/bastebin.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bastebin.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ── Security headers (supplement Flask's own headers) ────────────────────
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Robots-Tag "noindex, nofollow" always;
# ── Block access to sensitive paths ──────────────────────────────────────
# These should NEVER be served, even accidentally.
location ~* \.(py|json|db|sqlite|sqlite3|log|cfg|ini|toml|env|sh|conf|bak|orig)$ {
deny all;
return 404;
}
# Block dot-files / hidden files (e.g. .env, .git)
location ~ /\. {
deny all;
return 404;
}
# Block __pycache__ and similar Python internals
location ~* /__pycache__/ {
deny all;
return 404;
}
# ── Static files served directly by Nginx (fast path) ───────────────────
# Nginx serves these without hitting Gunicorn at all.
location /static/ {
alias /path/to/bastebin/static/;
# No directory listings
autoindex off;
# Cache busting — serve with long expiry; filename includes hash in prod
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
# Only allow known file extensions; block anything else
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
# Passthrough — let the alias serve it
}
location / {
# Anything under /static/ that doesn't match above is denied
deny all;
return 404;
}
}
# ── Gunicorn (Flask application) ─────────────────────────────────────────
location / {
proxy_pass http://unix:/run/bastebin/gunicorn.sock;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# Body size limit — match config.json max_size_bytes (2 MB) + overhead
client_max_body_size 3m;
}
# ── Logging ───────────────────────────────────────────────────────────────
access_log /var/log/nginx/bastebin.access.log;
error_log /var/log/nginx/bastebin.error.log warn;
}