Prerequisites
- A VPS with a public IP and a domain pointing to it
- An agent (OpenClaw, Hermes, etc.) running on localhost on a fixed port
- Root or sudo access on the VPS
Steps
- Install Nginx and certbot
Both are in the Ubuntu/Debian repos. Use the snap version of certbot for automatic renewal handling on long-lived hosts.
sudo apt update sudo apt install -y nginx sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/local/bin/certbot - Write a basic vhost
Replace 'agent.example.com' with your subdomain. The configuration below assumes the agent listens on 127.0.0.1:8765. Saves to /etc/nginx/sites-available/agent.
sudo tee /etc/nginx/sites-available/agent <<'EOF' upstream agent_backend { server 127.0.0.1:8765; keepalive 16; } # Rate-limit zone — 10 r/s per IP, burst 20. limit_req_zone $binary_remote_addr zone=agent:10m rate=10r/s; server { listen 80; server_name agent.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name agent.example.com; # Let's Encrypt will fill these in. ssl_certificate /etc/letsencrypt/live/agent.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/agent.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security headers. add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "no-referrer" always; # Body size — agents send small JSON, no need for 10 MB defaults. client_max_body_size 1m; location / { limit_req zone=agent burst=20 nodelay; proxy_pass http://agent_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 https; # WebSocket support (if your agent exposes one). proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300s; proxy_send_timeout 300s; } } EOF sudo ln -s /etc/nginx/sites-available/agent /etc/nginx/sites-enabled/ - Provision the certificate
Certbot will edit the vhost in place to insert the SSL paths. Use the nginx plugin so the running Nginx is reloaded automatically.
sudo certbot --nginx -d agent.example.com sudo nginx -t && sudo systemctl reload nginx - Verify
Hit the public URL. You should see the agent's response. Run a small flood to confirm rate limiting kicks in (you should get HTTP 503 after the burst).
for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" https://agent.example.com/; done
Troubleshooting
- Certbot can't get a certificate (DNS validation fails)
- Confirm the A/AAAA record points at the VPS public IP. Wait up to 5 minutes for DNS propagation. Re-run certbot.
- WebSocket connection from the agent dashboard fails
- Verify the Upgrade and Connection headers are in the Nginx config above. Some browser extensions also strip these headers — try Incognito.
- Rate limit triggers on legitimate dashboard usage
- Increase the rate (10r/s → 30r/s) or use a separate location block for the dashboard URL with no rate limit.
Where to go from here
Pair Nginx rate limiting with fail2ban for IPs that consistently hit 503. Consider Cloudflare in front of Nginx if you face automated scanning.