NovaPanel
Docs

Run NovaPanel behind a Cloudflare Tunnel

Homelab setup: expose the admin and customer panels through cloudflared without opening ports. Plus the honest list of what doesn't fit through an HTTP-only tunnel.

This guide is for homelabbers who want to expose NovaPanel without opening ports on their router. Cloudflare Tunnel (cloudflared) forwards traffic from Cloudflare's edge to a daemon running on your panel host, so nothing has to be publicly routable.

Cloudflare Tunnel only carries HTTP/HTTPS. Anything else NovaPanel serves (mail, FTP, authoritative DNS) needs a different solution. Read the table below before you commit to this setup.

What works, what doesn't

SurfaceVia Cloudflare Tunnel?Notes
Admin panel (port 2087)YesPoint a tunnel hostname at http://localhost:8087 (the panel's plaintext port — see note below)
Customer panel (port 2083)YesPoint a tunnel hostname at http://localhost:8083
Customer websites (Caddy 80/443)With caveats — see belowCaddy's automatic HTTPS depends on Let's Encrypt reaching port 80/443
Mail — Postfix SMTP (25/465/587)NoNot HTTP. Use a relay (SES, Mailgun, Postmark) or expose mail ports directly
Mail — Dovecot IMAP/POP (143/993/995)NoSame — not HTTP
FTP / FTPS (21 + passive range)NoUse SFTP over SSH instead, and tunnel SSH with cloudflared access ssh
Authoritative DNS (53)NoUse Cloudflare DNS for your zones and point NS records at Cloudflare

If your homelab use case is panel plus customer websites only, this setup works well. If you also need first-party mail or FTP, you'll need a public IP for those ports — Cloudflare Tunnel can't carry them.

Step 1 — Install cloudflared on the panel host

NovaPanel runs on Debian/Ubuntu. Install Cloudflare's official package:

sudo mkdir -p /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
  | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
  https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared

Authenticate against your Cloudflare account (this opens a browser):

sudo cloudflared tunnel login

Create the tunnel and grab its UUID:

sudo cloudflared tunnel create novapanel
# → Created tunnel novapanel with id <UUID>
# → Tunnel credentials written to /root/.cloudflared/<UUID>.json

Step 2 — Tunnel config (panel only)

Which port? The panel binary listens on plaintext 8083 (customer) and 8087 (admin); Caddy normally fronts those on 2083/2087 with its own TLS. Since Cloudflare terminates TLS at its edge, point the tunnel straight at the binary's plaintext ports — no local certificate to verify.

Drop this at /etc/cloudflared/config.yml:

tunnel: <UUID>
credentials-file: /root/.cloudflared/<UUID>.json

ingress:
  # Admin panel
  - hostname: admin.example.com
    service: http://localhost:8087
  # Customer panel
  - hostname: panel.example.com
    service: http://localhost:8083
  # Catch-all (required) — everything else gets a 404 from cloudflared
  - service: http_status:404

Route the hostnames through the tunnel:

sudo cloudflared tunnel route dns novapanel admin.example.com
sudo cloudflared tunnel route dns novapanel panel.example.com

Install and start the systemd unit:

sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared

The two hostnames now serve the panel over Cloudflare's edge TLS. NovaPanel doesn't need to bind to a public IP — localhost:8083 / localhost:8087 is enough.

Step 3 — Customer websites: choose one of two modes

NovaPanel's Caddy issues Let's Encrypt certificates for every customer domain using the HTTP-01 (port 80) or TLS-ALPN-01 (port 443) ACME challenges. When Cloudflare proxies a hostname (orange-cloud), the challenge requests never reach your origin — Caddy can't get a cert.

Two ways around it today:

Mode A — DNS proxy off (grey-cloud)

For domains served by NovaPanel's Caddy, set the Cloudflare DNS record to DNS only (the grey cloud icon, not the orange one). Cloudflare publishes the A/AAAA record but doesn't proxy the traffic — Let's Encrypt can hit port 80 on your origin directly, and Caddy issues a real public cert.

Trade-off: those hostnames lose Cloudflare's CDN, WAF, and DDoS protection. They still get DNS hosting. You still need ports 80 and 443 reachable on your origin for the grey-clouded domains, which usually means router port-forwarding for those two ports.

Mode B — Cloudflare Full (strict) with origin cert

Keep the orange cloud, but install a Cloudflare Origin CA cert on the origin. Cloudflare's edge trusts the Origin CA but browsers don't — so it only works behind Cloudflare. NovaPanel manages this for you: the customer panel's custom-certificate installer names Cloudflare Origin certificates explicitly.

  1. In Cloudflare → SSL/TLS → Origin Server, create a 15-year Origin CA cert covering *.example.com and example.com. Copy the certificate and the private key it shows you — the key is only displayed once.
  2. In the customer panelSSL Certificates, click Custom cert next to the domain. Paste the certificate and private key in PEM format (plus intermediates, if your CA gave you any) and click Install certificate.
  3. Caddy now serves HTTPS from that certificate for the domain and stops trying ACME. The panel stores the cert outside the generated Caddyfile and re-applies it on every rebuild, so re-saving the site doesn't clobber it. To go back to Let's Encrypt later, use Remove & revert to auto-SSL in the same modal.
  4. Set SSL/TLS mode to Full (strict) in the Cloudflare dashboard.
  5. Add the hostnames to your cloudflared ingress, pointing at https://localhost:443 with originServerName set to the customer domain.

This keeps the orange cloud (CDN, WAF, DDoS) and avoids opening 80/443 on your router, with no hand-edited Caddy configs.

Verification checklist

After setup, walk through these to confirm everything is wired up:

  • sudo systemctl status cloudflared → active (running)
  • https://admin.example.com opens the admin login
  • https://panel.example.com opens the customer login
  • A test customer site loads over HTTPS without a cert warning
  • Cloudflare → Network → "Free WAF rules" don't flag the panel's WebSocket endpoints (/api/v1/terminal/ws, /api/v1/ws) — if they do, add a bypass rule for those paths

Common gotchas

  • WebSockets idle out after 100s. Cloudflare's default WebSocket idle timeout is 100 seconds; the panel's terminal and notification streams will reconnect, but for long terminal sessions consider Cloudflare Zero Trust which lets you raise the timeout.
  • Large file uploads cap at 100 MB on Free plan. Cloudflare's body size limit applies to the File Manager and database imports. Pro bumps it to 200 MB, Business to 500 MB, Enterprise is configurable.
  • Impersonation redirects don't survive the tunnel. "Log in as customer" builds the customer-panel URL from the admin request's Host header: it keeps the admin hostname and only remaps the port. Behind a tunnel there's no port in the Host header, so it falls back to the panel's internal customer port and sends the browser to https://admin.example.com:8083/sso… — a port cloudflared isn't serving, so the redirect dead-ends. Pointing the two hostnames at the same root domain doesn't help; you'd need an ingress rule for the admin hostname on the customer port. Until then, impersonate from a direct connection to the panel instead.