# ── 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; }