Serve your site over HTTPS — terminate TLS in Nginx, force every visitor onto HTTPS, get free auto-renewing certificates with Certbot, and rate-limit abusive clients.
HTTPS encrypts traffic between the user and your server. "Terminating TLS" at Nginx means Nginx handles the encryption, then talks plain HTTP to your app behind it — so your app needs no TLS code. You listen on 443 with ssl, point to your certificate and its private key, and restrict to modern protocol versions.
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # drop old, insecure versions
location / {
proxy_pass http://127.0.0.1:3000;
}
}Visitors still type http:// or follow old links, so you keep a small server block on port 80 whose only job is to bounce them to the HTTPS version with a permanent 301 redirect. $host keeps the same domain and $request_uri keeps the exact path and query string, so the redirect lands on the right page.
# Plain-HTTP site: redirect everything to HTTPS.
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}You do not hand-write certificates. Certbot, from Let's Encrypt, obtains a free TLS certificate, edits your Nginx config to use it, and sets up automatic renewal (certs last 90 days). The --nginx plugin does all of it in one command, then a timer renews them quietly before they expire.
Install Certbot and its Nginx plugin (Debian/Ubuntu)
sudo apt install certbot python3-certbot-nginxGet a cert and wire it into your Nginx config automatically:
sudo certbot --nginx -d example.com -d www.example.comRenewal is automatic; confirm it works with a dry run:
sudo certbot renew --dry-runNginx can throttle clients before requests ever reach your app — a cheap first line of defense against brute-force and scraping. limit_req_zone defines a bucket keyed by client IP with an allowed rate; limit_req applies it, with burst allowing short spikes. Here, login attempts are capped at roughly 5 per second per IP.
# In http { }: 10 MB of state, max 5 requests/sec per IP.
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
server {
location /login {
limit_req zone=login burst=10 nodelay; # allow short bursts
proxy_pass http://127.0.0.1:3000;
}
}