Skip to content

Explore theLAB

← All field guides

Build and operate / Intermediate

Publish a Self-Hosted App Safely

Expose one harmless web app over HTTPS with an explicit access policy and private origin.

Allow
1-2 hours
Before you start
A domain managed in Cloudflare, cloudflared, Python 3, and authorized account access
Finish with
An authenticated public hostname without router port forwarding or database exposure.

Reviewed . Commands and examples are reference builds; check installed versions. Hardware-dependent steps are not certified as end-to-end tested.

What you will build

A harmless loopback web application exposed through a Cloudflare Tunnel under a dedicated hostname, with an explicit Cloudflare Access policy. No database, model API, or management interface is published.

Allow 1-2 hours. You need a domain connected to Cloudflare, permission to manage its DNS and Zero Trust settings, and a supported cloudflared installation. Check the provider's current plan limits and terms; owning the server does not make the domain or service plan free.

Before you start

Use a new hostname, such as lab-demo.example.com, that does not serve an existing application. This is an example domain: replace it with your own. Do not edit a production tunnel or delete an existing DNS record to make the lab fit.

Install cloudflared using Cloudflare's supported package instructions and record cloudflared --version. Use Python 3 and an unused port 18082 for the origin.

1. Start an intentionally harmless origin

Create a fresh directory with only a plain index.html containing a recognizable test message. From that directory:

python3 -m http.server 18082 --bind 127.0.0.1

This development server is only a demonstration origin, not a production web server. Do not run it from your home directory, repository root, backup directory, or any location containing secrets.

In another terminal:

curl --fail http://127.0.0.1:18082/
ss -ltn | grep 18082

Checkpoint: the expected page loads and the service listens only on loopback.

2. Define access before routing the hostname

In the Cloudflare Zero Trust dashboard, create a self-hosted public Access application for the exact demonstration hostname. Configure an allow policy for a specific authorized identity or group. Do not choose an everyone/bypass policy merely to make the first test easier.

Verify the identity-provider login method and keep a recovery route to the server through SSH. Access is an additional boundary, not a replacement for an application's own user-level authorization.

Configure the Access application before enabling the public application route, avoiding an unnecessary interval of open access.

3. Create a separate remotely managed tunnel

Use the dashboard's Networking / Tunnels workflow to create a new cloudflared tunnel for this lab. Follow the current OS-specific connector command shown by Cloudflare.

The tunnel token is a secret. Do not paste it into the public guide, a repository, a support screenshot, or shell history. Prefer the supported token-file mechanism where available and apply restrictive file permissions. Do not replace an existing system connector service for this demonstration.

Run a dedicated connector process or an independently named service, and verify it reports healthy. The connector must run on the same host as this loopback origin; localhost on another machine is a different destination.

4. Add the single application route

In the tunnel's published application routes, select the dedicated hostname and the origin service URL http://localhost:18082. Confirm the exact hostname is covered by your Access application, then save.

The dashboard can create the associated DNS record. Review the resulting record rather than adding conflicting records manually. Do not configure wildcard routing or publish database TCP services as part of this web-app exercise.

The browser-to-edge path uses HTTPS; the local origin hop in this example is loopback HTTP. For an origin on a different host, use a protected network and appropriate TLS verification rather than simply disabling certificate validation.

5. Test allowed and denied paths

Open the hostname in an anonymous browser: it should require Access authentication instead of showing the test page directly. Sign in as the allowed identity and verify the exact origin marker. Check a second unauthorized identity if available.

Check the host and router exposure separately. This tunnel does not close preexisting forwarded ports or unrelated public listeners. Database ports should not be reachable publicly, and the origin should remain loopback-only.

Test sign-out and a fresh browser session, not just an already authenticated tab.

When it breaks

An origin error calls for testing localhost and connector location. An authentication loop calls for reviewing the hostname, policy, identity-provider configuration, and cookies. A certificate issue is not a reason to disable verification globally.

Cleanup

Remove only the demonstration application route and its dedicated DNS record, stop its connector, then stop the Python origin. Remove the demonstration Access application after confirming it is unused. Leave all unrelated tunnels and policies untouched.

References