Public Access
Securely expose Suwu to the public internet using a reverse proxy, wildcard host validation, password authentication, and zero-trust identity providers.
Overview
Suwu is designed for local and LAN use by default. To expose it publicly, you need four layers of defense:
- Bind to localhost only — the server never directly faces the internet
- Wildcard host validation — reject requests for unexpected hostnames
- Password authentication — require credentials before granting access
- Zero-trust access — let Cloudflare (or your proxy) handle TLS and identity
Step 1 — Server Configuration
Edit ~/.config/suwu/.env on your server:
# Bind to localhost only — the server never touches the public internet directly.
HOST=127.0.0.1
# Allow only your domain. Supports wildcards:
# *.mydomain.com — matches any subdomain
# mydomain.* — matches any TLD
# * — matches everything (not recommended for public use)
EXTRA_HOSTS=*.mydomain.com
# Require a password for all API access.
AUTH_PASS=<sha256-hex-of-your-password>
# Cloudflare handles public HTTPS termination; use plain HTTP locally.
SERVER_MODE=http
HTTP_PORT=8180
Generate the password hash:
# Install shasum if needed, then:
echo -n "your-secret-password" | shasum -a 256 | awk '{print $1}'
HOST=127.0.0.1 ensures Suwu only listens on the loopback interface. Even if someone bypasses your reverse proxy, they cannot reach the server directly — there is no port exposed to the outside.
Step 2 — Reverse Proxy
You have two options depending on your TLS setup:
Option A — Cloudflare (recommended)
When using Cloudflare, TLS is terminated at the edge. Set SERVER_MODE=http in your .env and let Cloudflare handle certificates.
Option B — Your own certificates
If you prefer to manage TLS yourself (e.g., nginx with Let's Encrypt), use SERVER_MODE=https and provide valid certificates issued to your public-facing domain:
# In ~/.config/suwu/.env
TLS_CERT_FILE=/etc/letsencrypt/live/mydomain.com/fullchain.pem
TLS_KEY_FILE=/etc/letsencrypt/live/mydomain.com/privkey.pem
Then configure your reverse proxy to forward to https://127.0.0.1:8181.
Step 3 — Cloudflare Zero Trust Tunnel
A Cloudflare Tunnel connects your server to Cloudflare's edge without opening any inbound ports. This is the most secure approach — your server's port 8181 is never exposed to the public internet.
Install cloudflared
# Debian / Ubuntu
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
# Or install the binary directly
curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
Authenticate and create a tunnel
# Log in to your Cloudflare account (opens browser)
cloudflared tunnel login
# Create a named tunnel
cloudflared tunnel create suwu
# Note the tunnel ID from the output
Configure the tunnel
Create ~/.cloudflared/config.yml:
tunnel: <tunnel-id>
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: terminal.mydomain.com
service: http://127.0.0.1:8181
originRequest:
noTLSVerify: true
- service: http_status:404
When SERVER_MODE=http, the Suwu server runs plain HTTP on localhost at http://127.0.0.1:8180, so there's no TLS to verify on the origin side. Cloudflare still enforces HTTPS for all visitors.
Route DNS and run
# Route your subdomain to the tunnel
cloudflared tunnel route dns suwu terminal.mydomain.com
# Run the tunnel (or use systemd service below)
cloudflared tunnel run suwu
Run as a systemd service
# Install the service
sudo cloudflared service install
# Enable and start
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
Step 4 — Access Protection
At this point, anyone who knows terminal.mydomain.com can reach your Suwu server — but they still need the password. For stronger access control, use Cloudflare's identity features.
Option A — One Time PIN (simplest)
Cloudflare sends a PIN to the visitor's email address. No identity provider setup required.
- Go to Cloudflare Zero Trust Dashboard → Access → Applications
- Click Create an application
- Choose Self-hosted
- Set the application domain:
terminal.mydomain.com - Under Policy, create a policy with action Allow
- Add a One-time PIN criterion (email domain)
- Save
Now when someone visits https://terminal.mydomain.com, Cloudflare challenges them with a PIN sent to their email before they ever reach your server.
Option B — Identity Provider (stronger)
Connect a social or enterprise identity provider for SSO. This is ideal for personal or team use.
Google Login example
- Go to Cloudflare Zero Trust Dashboard → Settings → Authentication
- Click Add new under Login methods
- Choose Google
- Follow the OAuth consent screen setup (requires a Google Cloud project with OAuth credentials)
- Once connected, go to Access → Applications → Create an application
- Choose Self-hosted, set domain to
terminal.mydomain.com - Create a policy: Allow → Emails ending in
@yourcompany.com(or any Gmail) - Save
Visitors are now redirected to Google login before accessing your terminal.
With both Cloudflare access policies and Suwu's AUTH_PASS, an attacker must bypass two independent authentication layers: Cloudflare's identity check and the Suwu password. This is defense in depth.
Full Working Example
Here's a complete ~/.config/suwu/.env for a Cloudflare Zero Trust deployment:
# ~/.config/suwu/.env
HOST=127.0.0.1
EXTRA_HOSTS=*.mydomain.com
AUTH_PASS=a1b2c3d4e5f6... # sha256 of your password
SERVER_MODE=http
HTTP_PORT=8180
And the corresponding Cloudflare tunnel config:
# ~/.cloudflared/config.yml
tunnel: abc123-def456
credentials-file: /root/.cloudflared/abc123-def456.json
ingress:
- hostname: terminal.mydomain.com
service: http://127.0.0.1:8181
originRequest:
noTLSVerify: true
- service: http_status:404
Start both services:
# Start the Suwu server (via systemd or directly)
suwu daemon start
# Start the tunnel (via systemd)
sudo systemctl start cloudflared
Visit https://terminal.mydomain.com — you'll see Cloudflare's access challenge, then the Suwu password prompt, and finally your terminal.
Troubleshooting
| Symptom | Likely cause |
|---|---|
403 Forbidden on /api/token |
EXTRA_HOSTS doesn't match the Host header your proxy sends. Check what Host the proxy forwards — it must match a pattern in EXTRA_HOSTS. |
| Certificate warning in browser | SERVER_MODE=https is enabled, but the local certs don't match your public domain. Use SERVER_MODE=http when Cloudflare handles TLS. |
| Tunnel connects but 502 Bad Gateway | Suwu server is not running, or the tunnel points to the wrong port. Verify with curl http://127.0.0.1:8181/api/token on the server. |
| Cloudflare access page not showing | Access policy not applied to the application. Check Zero Trust Dashboard → Access → Applications. |