Publishing an Uptime Kuma Status Page with Cloudflare Tunnel

A Docker-based way to publish service status without opening an inbound port.

What this builds

This setup runs Uptime Kuma in Docker and publishes a public status page through Cloudflare Tunnel without forwarding a router port to your network. Cloudflare creates an outbound-only connection from your environment to Cloudflare, so the public hostname can point to the status page while the service itself stays behind your home or lab network.

The public status page may use a route such as /status/<slug>, or it can be selected as Uptime Kuma’s entry page. Either way, the design keeps the dashboard accessible while reducing the need to expose additional inbound ports.

Important: publishing a hostname can also expose Uptime Kuma’s authenticated login surface. A tunnel does not make the admin interface private by itself.

How the traffic flow works

A user visits https://status.example.com. Cloudflare receives the request at the edge, then forwards it across the outbound tunnel connection to a local cloudflared process. That tunnel then proxies the request to the Uptime Kuma application running inside the Docker network.

In this setup, the published application service URL is http://uptime-kuma:3001. That hostname is only valid inside the shared Docker network. It is not a private IP address, internal domain, or a public host you should expose directly. The reason localhost is wrong here is that each container has its own loopback interface; when cloudflared runs in a separate container, localhost would refer to the Cloudflare connector itself, not the Uptime Kuma service.

No router port forwarding is required because cloudflared creates an outbound connection from your environment to Cloudflare. The inbound public traffic is handled by Cloudflare, not by opening a port on your home router or firewall.

Prerequisites

  • Docker Engine and Docker Compose installed on the host.
  • A Cloudflare account with a domain that you control.
  • Permission to create a Cloudflare Tunnel and published application route.
  • Access to the Uptime Kuma admin interface once it is running.

Create the Uptime Kuma Docker project

Create a project directory for the deployment and add a .env file for your tunnel secret. The tunnel token is a secret and must never be committed. Keep it out of source control and rotate it if it is ever exposed.

mkdir uptime-kuma-cloudflare-tunnel cd uptime-kuma-cloudflare-tunnel mkdir -p data cat > .env <<'EOF' TUNNEL_TOKEN=replace-with-your-tunnel-token EOF chmod 600 .env cat > .gitignore <<'EOF' .env data/ EOF

The .env file should remain local to the machine. Set the file permissions with chmod 600 .env so it is not readable to other local users.

Do not expose Docker socket access in the stack. The application should use the normal Docker network and container-to-container communication only.

Start Uptime Kuma locally

Use the following Docker Compose example to run both Uptime Kuma and Cloudflare Tunnel on the same network. The Uptime Kuma data directory is persisted locally with a bind mount, and both containers restart automatically if the host reboots.

services: uptime-kuma: image: louislam/uptime-kuma:2 container_name: uptime-kuma restart: unless-stopped volumes: - ./data:/app/data ports: - "127.0.0.1:3001:3001" networks: - uptime-net cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared restart: unless-stopped depends_on: - uptime-kuma command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN} networks: - uptime-net networks: uptime-net: driver: bridge

Start only Uptime Kuma before the real tunnel token exists:

docker compose up -d uptime-kuma

Binding to 127.0.0.1 prevents port 3001 from being exposed to the LAN or the Internet. This means the dashboard is only reachable from the Docker host itself while the tunnel is being created and configured.

When the browser is running on the Docker host, open http://127.0.0.1:3001 to complete the Uptime Kuma initial setup. For a remote Docker host, you can create an SSH tunnel like this:

ssh -L 3001:127.0.0.1:3001 YOUR-USER@YOUR-SERVER

After creating the SSH tunnel, open http://127.0.0.1:3001 locally in your browser. The Cloudflare Tunnel is separate and should not be used as the local admin endpoint.

Create the public status page

After the first login, create a status page in Uptime Kuma and select the monitors you want to show. The public page can be accessed at a route like /status/<slug> or selected as the Uptime Kuma instance’s entry page in the status page settings.

Keep the public page focused on service health and avoid exposing unrelated administrative areas. If you publish a hostname that points at the Uptime Kuma app, the login surface may also become reachable from the public hostname.

Create a Cloudflare Tunnel

In Cloudflare Zero Trust, navigate to Networking → Tunnels → Create a tunnel. Create the tunnel, then copy only the generated tunnel token into your local .env file. Replace the placeholder value with the token you generated, and do not display or share a real token in the article or terminal history.

The tunnel will connect outbound from your host to Cloudflare, and the tunnel token is what authorizes that connection. Cloudflare will then let you create a public hostname such as status.example.com and route it to the local application behind the tunnel. The tunnel behaves like a secure outbound channel rather than a port opened on your firewall.

Store the tunnel token safely

Store the value in .env as TUNNEL_TOKEN=..., and keep the file on the host only. If anyone can read the file, they can use the tunnel credentials. Treat the token as a secret and rotate it immediately if it is ever exposed in logs, screenshots, source control, or shared documents.

After the token is stored, start or recreate both services with:

docker compose up -d docker compose ps

This loads the real token from .env and brings the tunnel and application back online together. Keep the token out of Git history and never commit it. This is especially important for a public project or a shared repository where a leaked token could be used to create or reuse the tunnel.

Set the file permissions with chmod 600 .env so it is not readable to other local users.

Route the public hostname

Once the tunnel is connected, create a public hostname in the Cloudflare dashboard. Use something like status.example.com and route it to the application service URL http://uptime-kuma:3001.

If the service is not reachable through the tunnel, check if the Cloudflare Tunnel is connected and confirm that the app is listening on port 3001 inside the Docker network. A Docker network issue or a wrong service address will often show as a 502 or 522 response from Cloudflare.

Verify from outside the network

After the tunnel and hostname are configured, test the site from a different network or an incognito browser window. This helps confirm that the public page is being served by Cloudflare rather than your local browser cache or nearby network path.

Use a public check such as another laptop on cellular data or a fresh browser profile to confirm that traffic is flowing through the tunnel as expected. If the status page works from outside but not from your local network, the issue is often browser caching or the local host route rather than the tunnel itself.

Secure the administrative login

Because a public hostname can surface the login page as well as the status page, choose a unique administrator password and turn on Uptime Kuma 2FA before publishing. Do not reuse a weak or shared password for the admin account, and keep the public-facing service separate from any internal-only services you might run later.

This is not a substitute for a private network or a separate internal-only deployment, but it is a sensible step before making status information public. A Cloudflare Tunnel reduces inbound exposure, but it does not automatically make the Uptime Kuma dashboard private.

Troubleshooting

  • Tunnel shows healthy but the site returns 502 or 522: A 502 normally means the tunnel is connected but cloudflared cannot reach the configured service. Confirm that the tunnel is routing to the correct local target, and verify that the remote source is http://uptime-kuma:3001, not localhost or a private host that is only inside the browser. For a 522, check that the hostname is associated with the tunnel and is not still pointing at an old external origin.
  • Using localhost instead of the Docker service name: When cloudflared runs in a separate container, localhost points to the cloudflared container itself. Use the Docker service name uptime-kuma instead.
  • Containers not sharing a Docker network: Make sure both services are attached to the same Compose network. A mismatched network layout will block the tunnel from reaching the app.
  • Public hostname opens the login page instead of the status page: Check the Uptime Kuma entry page setting and confirm the status page slug is configured correctly. The public hostname may expose the login screen if the status page is not selected as the portal entry.
  • Tunnel token exposure and rotation: If the token is ever leaked, revoke or rotate it in Cloudflare and replace the secret in the local .env file. Treat it like a production secret.
  • Testing with an incognito window or a different network: This helps rule out browser cache or a stale local route while validating the public entry point.

Final checklist

  • Uptime Kuma runs in Docker with a persistent ./data:/app/data bind mount.
  • Both containers share the same Docker network.
  • The Cloudflare tunnel token is stored in a local .env file and not committed.
  • The tunnel command is tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}.
  • The public hostname routes to http://uptime-kuma:3001.
  • The router does not need a forwarded inbound port because cloudflared connects outbound.
  • Uptime Kuma’s authentication is secured before the hostname is published widely.

Official documentation