Reverse proxy
Why do I need a reverse proxy?
Section titled “Why do I need a reverse proxy?”When you deploy Laju Go, the app listens on a port (default 8080) and
speaks plain HTTP — no encryption. If a user visits
http://your-server-ip:8080, their browser connects directly to your
server and everything (passwords, session cookies, form data) travels
over the internet in plaintext. Anyone on the path can read it.
You need HTTPS (https://your-domain.com — the lock icon). That
requires three things the app doesn’t do itself:
- TLS certificate — proves your server is really
your-domain.comand encrypts traffic. Browsers refuse to show the lock icon without it. - Listen on port 443 (HTTPS) instead of
8080— browsers default to 443 forhttps://URLs. Port 8080 is non-standard; users would have to type:8080manually. - A way to get the certificate and renew it — certs expire every 90 days (Let’s Encrypt) or are managed for you (Cloudflare).
A reverse proxy sits between the internet and your app, handles all three, and forwards traffic to your app on port 8080:
User's browser │ HTTPS (port 443, encrypted) ▼Reverse proxy (TLS cert, port 443) │ HTTP (port 8080, local only) ▼Laju Go (Go binary, port 8080)The app never sees the internet directly — the proxy terminates TLS, then forwards the decrypted request locally. Your app stays simple (no cert management, no TLS config), and the proxy handles the parts it’s good at.
Which one? If your domain is on Cloudflare, use that — it’s the proxy, nothing to install. Otherwise, Caddy (auto-TLS via Let’s Encrypt) or Nginx + Certbot both work. Details below.
Laju Go listens on a single port (default 8080). It does not terminate
TLS — that is the proxy’s job. Whether you run Docker or bare-metal, the
proxy sits in front and forwards to 127.0.0.1:8080.
Internet → [Cloudflare edge | Caddy | Nginx] → 127.0.0.1:8080 └→ Laju Go (Go binary)Pick one option. All three work with both Docker and bare-metal VPS deployments.
Option A — Cloudflare (recommended, nothing to install)
Section titled “Option A — Cloudflare (recommended, nothing to install)”Your domain is on Cloudflare, so the edge handles TLS and proxying; the
server only runs the Go binary. Two routes are available — A1
(Proxied DNS + Origin Rule) is recommended as the default: no extra
software, no open ports beyond what Cloudflare uses, and the Origin
Rule ensures the app sees the correct Host header for CSRF and OAuth.
A2 (Tunnel) is the alternative when you want zero inbound ports at all.
A1. Proxied DNS + origin rule (no extra software)
Section titled “A1. Proxied DNS + origin rule (no extra software)”Cloudflare proxies traffic to your server and handles TLS. No software to install on the server — just DNS and dashboard settings.
Step 1 — DNS record:
- Cloudflare Dashboard → DNS → Records.
- Add an A record: name
your-domain.com(or@), content = your server IP, proxy status Proxied (orange cloud).- “Proxied” means traffic goes through Cloudflare’s edge (TLS, DDoS protection, caching). “DNS only” (grey cloud) just resolves the IP and exposes your server directly — don’t use that.
- (Optional) Add a
wwwCNAME pointing toyour-domain.com, also proxied.
Step 2 — SSL/TLS mode:
Dashboard → SSL/TLS → Overview → set mode to Flexible:
- Flexible (recommended) — Cloudflare terminates TLS at the edge, talks HTTP to your origin on port 8080. No cert needed on the server. This is the simplest setup and works well for boilerplate apps like Laju Go. Safe because the firewall only allows Cloudflare IPs (see Firewall below).
- Full (optional) — edge→origin over HTTPS. You need a cert on the origin (Cloudflare Origin CA cert, free from Dashboard → SSL/TLS → Origin Server → Create Certificate). More secure for sensitive apps, but more setup. Skip this unless you have a specific reason.
Multiple domains? Flexible mode is per-zone (per-domain) in Cloudflare, so each domain you point at the same server gets its own TLS at the edge — no extra cert management. Just add another A record and Origin Rule per domain. The same Laju Go instance serves all of them on port 8080.
Step 3 — Origin Rule (set this up now, don’t wait for it to break):
Cloudflare normally passes the original Host header through, but some
configurations (CNAME setups, partial proxy, Cloudflare Workers) can
rewrite it. Laju Go’s CSRF check compares the Origin header against
Host, and OAuth redirect URIs depend on the correct host — so a
rewritten Host header breaks both silently. Set up the Origin Rule
upfront:
- Dashboard → Rules → Origin Rules → Create rule.
- Name:
Rewrite host for laju-go. - If incoming requests match:
Hostname equals your-domain.com. - Rewrite Host header to:
your-domain.com. - Deploy.
This guarantees the app sees your-domain.com as the Host on every
request — CSRF passes, OAuth callbacks resolve correctly, and redirect
URLs point to the right domain. It’s one rule that prevents a class of
bugs that are hard to debug because they only manifest on specific
Cloudflare configurations.
Step 4 — Firewall: restrict port 8080 to Cloudflare IPs only (see Firewall below) so nobody can bypass the edge and hit your origin directly.
A2. Cloudflare Tunnel (alternative — zero open ports)
Section titled “A2. Cloudflare Tunnel (alternative — zero open ports)”Nothing to install except cloudflared:
# on the servercurl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflaredchmod +x /usr/local/bin/cloudflaredcloudflared tunnel login # one-time browser authcloudflared tunnel create laju-go# run the tunnel as a service pointing at 127.0.0.1:8080cloudflared service installDashboard → Zero Trust → Networks → Tunnels → add a public hostname
your-domain.com → service http://127.0.0.1:8080. No firewall rules
needed — the tunnel connects outbound.
Hostheader still matters. Even through a tunnel, set the Origin Rule (A1 Step 3) so CSRF and OAuth see the correctHost. Without it, some tunnel configurations passlocalhost:8080as the host, which breaks CSRF validation silently.
Option B — Caddy (no Cloudflare)
Section titled “Option B — Caddy (no Cloudflare)”Caddy handles TLS automatically via Let’s Encrypt.
/etc/caddy/Caddyfile:
your-domain.com { reverse_proxy 127.0.0.1:8080}sudo systemctl enable --now caddyCaddy passes the Host header through by default, so CSRF and OAuth
work without extra configuration.
Option C — Nginx + Certbot (no Cloudflare)
Section titled “Option C — Nginx + Certbot (no Cloudflare)”/etc/nginx/sites-available/laju-go:
server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; }}sudo ln -s /etc/nginx/sites-available/laju-go /etc/nginx/sites-enabled/sudo nginx -t && sudo systemctl reload nginxsudo certbot --nginx -d your-domain.comThe proxy_set_header Host $host; line is critical — without it, Laju
Go’s CSRF check sees the proxy’s internal address instead of the real
domain, and all POST requests fail with a 403.
Firewall
Section titled “Firewall”Cloudflare route (A1 — origin rule)
Section titled “Cloudflare route (A1 — origin rule)”Only Cloudflare IPs may reach the app port — this prevents anyone from bypassing the edge and hitting your origin directly. The command below fetches the current IP list once from Cloudflare’s API and creates persistent UFW rules. It does not run per-request; the rules survive reboots. But Cloudflare’s ranges do change, so set up a weekly refresh (see below).
One-time setup:
sudo ufw allow OpenSSH# Fetch live Cloudflare IP ranges (IPv4) and create UFW rulescurl -s https://api.cloudflare.com/client/v4/ips | jq -r '.result.ipv4_cidrs[]' | \ xargs -I{} sudo ufw allow from {} to any port 8080sudo ufw enableWeekly refresh — Cloudflare adds new IP ranges occasionally. Create
/etc/cron.weekly/refresh-cloudflare-ufw:
#!/bin/bash# Remove old Cloudflare rules for port 8080, re-add from live APIufw status numbered | grep '8080' | awk -F'[][]' '{print $2}' | sort -rn | \ xargs -I{} ufw delete {}curl -s https://api.cloudflare.com/client/v4/ips | jq -r '.result.ipv4_cidrs[]' | \ xargs -I{} ufw allow from {} to any port 8080sudo chmod +x /etc/cron.weekly/refresh-cloudflare-ufwSimpler alternative: use A2 (tunnel) — no inbound ports at all, no firewall rules to maintain.
No Cloudflare (options B/C)
Section titled “No Cloudflare (options B/C)”sudo ufw allow OpenSSHsudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw enablePort 8080 must NOT be exposed publicly — only 80/443 reach the app.
Docker
Section titled “Docker”If running Docker, map the port to localhost only so the proxy can reach it but the outside world cannot:
ports: - "127.0.0.1:8080:8080"Then apply the firewall rules above as if it were bare-metal.