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:

  1. Bind to localhost only — the server never directly faces the internet
  2. Wildcard host validation — reject requests for unexpected hostnames
  3. Password authentication — require credentials before granting access
  4. 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}'
Security note

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:

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
Why noTLSVerify: true?

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.

  1. Go to Cloudflare Zero Trust Dashboard → Access → Applications
  2. Click Create an application
  3. Choose Self-hosted
  4. Set the application domain: terminal.mydomain.com
  5. Under Policy, create a policy with action Allow
  6. Add a One-time PIN criterion (email domain)
  7. 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

  1. Go to Cloudflare Zero Trust Dashboard → Settings → Authentication
  2. Click Add new under Login methods
  3. Choose Google
  4. Follow the OAuth consent screen setup (requires a Google Cloud project with OAuth credentials)
  5. Once connected, go to Access → Applications → Create an application
  6. Choose Self-hosted, set domain to terminal.mydomain.com
  7. Create a policy: Allow → Emails ending in @yourcompany.com (or any Gmail)
  8. Save

Visitors are now redirected to Google login before accessing your terminal.

Double protection

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.