HostleloBlogExplore hosting

Host a Site From Home Safely With Cloudflare Tunnel

Publish an app from a home PC or small VPS with no open ports, lock a private tool behind an email PIN with Cloudflare Access and check the tunnel is Healthy.

On this page

The short answer

Install cloudflared on the machine that runs your app, create a tunnel in Networking > Tunnels, and add a Published application route that points a subdomain at a local address such as http://localhost:8000. Because cloudflared only makes outbound connections, you can delete your router port forwards and keep the origin IP private. For private tools, create a Cloudflare Access application with an Allow policy for your email and the One-time PIN login, then confirm the tunnel shows Healthy and the old port is closed from outside.

Yes, you can put a web app from your home PC, a mini server or a small VPS on the internet without opening a single inbound port on your router or firewall. Cloudflare Tunnel does it with a small program called cloudflared that dials out to Cloudflare, so visitors reach Cloudflare and never your home IP address. Put Cloudflare Access in front of a private tool, and only the email addresses you approve get past its login page.

Picture a setup many home-server owners will recognise. Someone opened port 8080 on their router two years ago to show a friend a dashboard, then forgot about it. The friend is long gone, but the port forward is still there, pointing at a machine that has not been patched since.

By the end of this guide you will have a public app on app.example.com, a private tool on tools.example.com that asks for an emailed one-time PIN, a tunnel showing Healthy, and proof that your origin port is closed to the outside world.

What a Cloudflare Tunnel actually is

A tunnel is a private, encrypted connection that your server starts on its own, going outward to Cloudflare. Cloudflare's own docs describe it like this: "Cloudflare Tunnel uses an outbound-only connection model to enable bidirectional communication."

Think of a shop that keeps its front door locked. The owner phones a delivery company each morning and keeps the line open. Customers order through the delivery company, the orders come down that line, and nobody ever needs the shop's street address.

The program on your server is cloudflared (a connector, meaning software that keeps that line to Cloudflare open). Your server is the origin, meaning the place where the app really runs. Because cloudflared starts the connection, it works even when your machine has no publicly routable IP address, which is common on home internet behind a router or carrier NAT.

Why this matters: with no port forward on the router, a random scanner on the internet has nothing to knock on. Cloudflare's docs go further and suggest you "configure your firewall to allow only these outbound connections and block all inbound traffic".

Now the honest part, because a tunnel is good at one job and only one job:

  • Your domain must be added to Cloudflare. The simplest path is a full setup, where Cloudflare runs your DNS and creates the tunnel's DNS records for you. A partial (CNAME) setup also works, but you add the records yourself at your DNS provider.
  • Your origin still needs updates. If the app has a security hole, a tunnel delivers attackers to it just as politely as it delivers your visitors.
  • A tunnel is not a backup. If the disk dies, the tunnel has nothing to serve.
  • A tunnel is not a web application firewall and not a replacement for strong passwords in the app itself.

New to where websites live? Our guide on what web hosting is is a good warm-up.

Before you start

Give yourself about 30 to 45 minutes. You add a new path to your app without changing the app, and you delete the old port forwards only at the end.

  • A domain added to Cloudflare and active, for example example.com. If you just moved your nameservers, read why DNS changes take time to show up before you panic.
  • A Cloudflare account login with permission to create tunnels and Access applications.
  • A web app running on the machine, reachable locally, for example on http://localhost:8000.
  • Shell access to that machine with sudo. This guide shows Debian and Ubuntu commands.
  • Outbound access to Cloudflare on port 7844. Most firewalls allow outbound traffic by default, but a strict office or VPS firewall might not.
  • Your router admin login, so you can delete old port forwards at the end.
  • A backup of your app's data and config, because the cleanup step closes ports and you want an easy way back if something else depended on them. A simple copy is enough, for example sudo tar czf ~/app-backup.tar.gz /etc/myapp /var/lib/myapp with your own paths.
  • A second network for testing, such as your phone on mobile data.

Quick tip: start with a throwaway subdomain like test.example.com, so a mistake costs you one route.

Part 1: Create the tunnel and connect your server

At the time of writing (18 October 2026), the dashboard labels below match Cloudflare's "Create a tunnel (dashboard)" guide. Cloudflare moves menus now and then, so if a label looks different, look for the closest match.

Step 1: Create the tunnel in the dashboard

Log in to the Cloudflare dashboard and go to Networking > Tunnels, then select Create a tunnel.

Enter a name that tells future you what it connects, for example home-mini-server, then select Create Tunnel.

Choose your operating system. The dashboard now shows an installation command for that system. Keep this tab open.

Step 2: Install cloudflared from Cloudflare's package repository

On Debian or Ubuntu, Cloudflare's package site lists these commands. The first block adds Cloudflare's signing key and package source so your system can trust and fetch the package.

sudo mkdir -p --mode=0755 /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 any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list

This next command refreshes the package list and installs cloudflared.

sudo apt-get update && sudo apt-get install cloudflared

Why this matters: from the repository, cloudflared updates with your normal apt upgrades.

If you use another operating system, use the install command the dashboard shows for it.

Step 3: Run the connector as a service

Go back to the dashboard tab and copy the command that installs cloudflared as a service. It is specific to your tunnel, so always copy it from your own dashboard.

For reference only, the command has roughly this shape. The <TOKEN> part is a placeholder, not a real value, and you should not type this line by hand.

sudo cloudflared service install <TOKEN>

Run the command you copied. Then check that the service is running on Linux with this command.

systemctl status cloudflared

You should see the service listed as active and running. Back in the dashboard, wait for the connection to appear, then select Continue.

Heads up: treat that copied command as a secret, because it is what links this machine to your tunnel. Do not paste it into chat, screenshots or a public Git repository.

Heads up: only one cloudflared service can run on a machine. If you see cloudflared service is already installed, add more routes to the tunnel you already have instead of making a second service.

Part 2: Publish a public app through the tunnel

This is for something you are happy for anyone to visit, like a portfolio site or a small blog. If the app is a private tool, jump to Part 3 first, because Cloudflare recommends creating the Access application before the route.

Step 4: Make the app listen only on localhost

Your app should listen on 127.0.0.1 (the machine talking to itself), not on every network interface. Then even a VPS with a public IP keeps the port private, while cloudflared on the same machine still reaches it.

This command shows which programs listen on port 8000 and on which address.

sudo ss -tlnp | grep 8000

If you see 127.0.0.1:8000, good. If you see 0.0.0.0:8000 or *:8000, the app listens on all interfaces. Change the bind address in your app's settings (often a host or listen option) and restart it.

Step 5: Add a published application route

Go to Networking > Tunnels, select your tunnel, open the Routes tab, select Add route, then select Published application.

Fill in the fields like this:

  • Subdomain: app
  • Domain: example.com, picked from the drop-down
  • Service URL: http://localhost:8000

Select Add route. With a full DNS setup, Cloudflare creates the DNS record for app.example.com for you.

Two details that trip people up. A path you add here routes matching requests but does not strip or rewrite the path, so your app receives the full path. And a multi-level subdomain such as a.b.example.com needs an Advanced Certificate, so stick to one level, like app.example.com.

Heads up: if your app already serves HTTPS itself, or redirects HTTP to HTTPS, do not use an http:// Service URL. See the redirect loop entry in Troubleshooting before you save.

Step 6: Confirm the tunnel is Healthy

Go back to Networking > Tunnels. Your tunnel should show Healthy, which Cloudflare defines as active and serving traffic through four connections to its network.

One important catch: Healthy only means cloudflared can talk to Cloudflare. It does not prove that cloudflared can reach your app. That is why we test the hostname too, below.

Part 3: Lock a private tool behind Cloudflare Access

Cloudflare Access is a login gate that sits at Cloudflare, in front of your app. Requests that do not pass the gate are stopped at Cloudflare, as long as nothing else can reach your server directly, which is why Step 11 and Check 4 matter.

Made-up example: Priya runs a small file manager on a mini PC at home. Its own login is weak, so she does not trust it on the open internet. With Access, the internet only sees a Cloudflare login page that emails a PIN to her address.

Step 7: Turn on the one-time PIN login method

New Zero Trust organizations use the Cloudflare identity provider by default, and one-time PIN is no longer added automatically, so check it first.

Go to Zero Trust > Integrations > Identity providers. Under Your identity providers, select Add new identity provider, then select One-time PIN.

You should now see One-time PIN in your list of identity providers.

Step 8: Create the Access application

Go to Zero Trust > Access controls > Applications and select Create new application.

Select Self-hosted and private, then Add public hostname. Enter tools as the subdomain and pick example.com from the Domain dropdown.

Why this matters: Cloudflare states that "All Access applications are deny by default", which means a user must match an Allow policy before they get in. So until you add the policy in the next step, nobody gets in, including you.

Step 9: Add an Allow policy for your email

Under Access policies, create a new policy. Set it up like this:

  • Policy name: Owner only
  • Action: Allow
  • Rule type: Include
  • Selector: Emails
  • Value: you@example.com

Include works like OR, so if you add a second email, either person can log in. Require works like AND, and Exclude works like NOT. For a team, the selector "Emails ending in" with @example.com lets in everyone at your company domain.

Under identity providers for the app, make sure One-time PIN is selected. Set a Session Duration you are comfortable with, for example 24 hours, then select Create.

Quick tip: do not use a Bypass policy to "make it easy for yourself". Cloudflare warns that Bypass does not enforce any Access security controls and those requests are not logged.

Step 10: Route the private tool through the tunnel

Now add the route, exactly like Step 5. Go to Networking > Tunnels, select your tunnel, then Routes > Add route > Published application.

Use subdomain tools, domain example.com, and the local address of the tool as the Service URL, for example http://localhost:9000. Select Add route.

For an extra layer, Cloudflare suggests validating the Access token at the origin, so a request that somehow skipped Access is rejected. One way is to turn on Protect with Access in the tunnel route settings.

Even behind Access, keep a real password on the tool itself. If your control panel is involved, our guide to two-factor authentication in cPanel, Plesk and DirectAdmin covers that second lock.

Step 11: Delete the old router port forwards

Back up your router settings first if your router has an export option. Then log in to the router, find the port forwarding section (the name differs by brand), and delete any rule that sends ports such as 80, 443 or your app port to this machine.

On a VPS, close the app port in your provider's firewall or your server firewall, but leave SSH open the way you normally reach the server, or you will lock yourself out.

How to check it worked

Check 1: The tunnel status

In Networking > Tunnels, the tunnel should say Healthy. If it says Inactive, Down or Degraded, see Troubleshooting.

Check 2: The public hostname answers through Cloudflare

Run this from any computer to fetch only the response headers for the public app.

curl -I https://app.example.com/

You want to see a success status such as HTTP/2 200, plus server: cloudflare and a cf-ray header. Those last two show the answer came through Cloudflare.

Check 3: The private tool asks for a PIN

Open https://tools.example.com/ in a private browser window. You should see the Cloudflare Access login page, not your tool.

Enter your approved email and select Send login code. The PIN arrives by email and expires 10 minutes after you request it. Paste it, select Sign in, and you should land on your tool.

Now try an email that is not in the policy. The page still says A code has been emailed to you, but nothing arrives. That is on purpose, so nobody can use the form to discover which emails are allowed.

Check 4: The origin port is closed from outside

First, find your public IP from the origin machine. This command asks Cloudflare which address your request came from and prints the ip= line.

curl -s https://www.cloudflare.com/cdn-cgi/trace | grep ip=

Now switch to a different network, for example your phone on mobile data with the hotspot shared to a laptop. Try to reach the app port directly. The example below uses 203.0.113.25 as a stand-in for your real public IP.

curl -I --max-time 10 http://203.0.113.25:8000/

You want this to fail with a timeout or Connection refused. If it returns your app's headers, the port is still open somewhere.

For a second opinion, the free checker portchecker.co tests a port from its own servers. Enter your public IP and the app port, and it should report the port as closed. Check 80 and 443 too if you used to forward them.

Troubleshooting

Tunnel status shows Inactive

Symptom: the tunnel exists but shows Inactive. Cause: the connector was never run. Fix: install and run cloudflared on the origin. You can get the install command again under Networking > Tunnels, by selecting the tunnel and then Add a replica on the Overview tab.

Tunnel status shows Down

Symptom: it worked yesterday, now it shows Down, and visitors may see a Cloudflare 1033 error page. Cause: the cloudflared process stopped, the machine is off, or the network changed. Fix: run systemctl status cloudflared, start it with sudo systemctl start cloudflared if it is stopped, and check that the PC did not go to sleep.

Tunnel status shows Degraded

Symptom: Degraded. Cause: cloudflared runs, but at least one of its connections failed. Fix: check the logs and make sure your firewall allows outbound traffic to Cloudflare, including port 7844.

502 Bad Gateway from Cloudflare

Symptom: a 502 Bad Gateway page saying Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared. Cause: the tunnel is fine, but cloudflared cannot reach your app. Usually the app is not running, the port in the Service URL is wrong, or the protocol is wrong. Fix: on the origin, run curl -v http://localhost:8000 with your port. If that fails, start the app. If it works, compare the port and http or https with your Service URL.

Redirect loop on an HTTPS origin

Symptom: the browser shows ERR_TOO_MANY_REDIRECTS. Cause: your app redirects HTTP to HTTPS, but the route's Service URL is http://, so every request arrives over HTTP and gets redirected again. Fix: edit the route under Networking > Tunnels > your tunnel > Routes > Edit route, set the Service URL to https://localhost:443, then under Additional application settings > TLS set Origin Server Name to the hostname on your certificate, such as app.example.com. Keep Disable TLS certificate verification turned off. Cloudflare is clear on one more point: do not switch the zone to Flexible SSL to fix this. If you have met origin certificate errors on shared hosting before, our Cloudflare error 526 guide explains the certificate side.

Access login loop

Symptom: you enter the PIN, then land back on the login page, or see Invalid session. Please try logging in again. Cause: Access could not read its session cookie. Browser privacy tools, a Firefox Private Window, or security software can block it. Fix: finish the login in the same browser window you started it in, try a normal window, and exempt your app domain and your team domain from tracking protection.

Access denied for someone who should be allowed

Symptom: the user sees That account does not have access, or the PIN email never arrives. Cause: their email does not match an Allow policy, often because of a typo, a different address, or an AND rule (Require) they do not meet. Fix: open the application's policy and check the exact address. If they see This One-Time PIN has already been used, an email scanner probably clicked the link first. Ask them to select Request new code and allowlist noreply@notify.cloudflare.com in their mail filter.

An A, AAAA or CNAME record already exists

Symptom: saving the route fails with An A, AAAA, or CNAME record with that host already exists. Cause: an old DNS record uses the same name. Fix: pick another subdomain, or delete the old record in DNS > Records after you are sure nothing else uses it.

Pro tips and when to ask for help

  • Keep cloudflared updated through your normal apt upgrades, the same as every other package on the machine.
  • If your home PC sleeps, the tunnel goes Down. Turn off sleep for a machine that serves sites.
  • Ask a developer for help if your app needs WebSockets, live streaming responses, or path rewrites. Each has its own settings worth getting right.

The one message worth forwarding

Copy this to the person who looks after your server:

"Hi, I moved our app off the router port forward and onto Cloudflare Tunnel. Public app: app.example.com. Private tool: tools.example.com, protected by Cloudflare Access with email one-time PIN. The tunnel shows Healthy and the old port is closed from outside. Can you confirm the app only listens on 127.0.0.1, that its own login and updates are still in place, and that we have a working backup? The tunnel does not cover those."

The takeaway: delete the port forward, let cloudflared dial out, put Access in front of anything private, and keep patching and backing up the machine behind it.

Reader questions

Does Cloudflare Tunnel need a static or public IP address?

No. The cloudflared connector makes an outbound connection to Cloudflare, so it works even when your machine has no publicly routable IP address.

Can I use Cloudflare Tunnel if my domain's DNS is somewhere else?

The domain must be added to Cloudflare. With a full setup Cloudflare creates the tunnel DNS records for you; with a partial CNAME setup you add a CNAME record at your own DNS provider by hand.

Does a tunnel make my app secure on its own?

No. It hides the origin and removes open ports, but the app still needs updates, its own strong login and backups. A tunnel is also not a web application firewall.

How long is the Cloudflare Access one-time PIN valid?

The PIN expires 10 minutes after the initial request, and each PIN can be used only once. Requesting a new PIN cancels the previous one.

Why did a user not get the PIN email?

Cloudflare only sends the email if the address matches an Allow policy, even though the page always says a code was sent. Check the policy for typos, and ask the user to allowlist noreply@notify.cloudflare.com in their mail filter.

My tunnel is Healthy but the site does not load. Why?

Healthy only means cloudflared is connected to Cloudflare. It does not test whether cloudflared can reach your app, so check the Service URL, the port and whether the app is running.

Can I run two cloudflared services on one machine?

Only one cloudflared instance can run as a service on a machine. Add more routes to the existing tunnel instead of installing a second service.

Sources & further reading

  1. Cloudflare Tunnel overview
  2. Create a tunnel (dashboard)
  3. Cloudflare Package Repository
  4. Run cloudflared as a service on Linux
  5. Publish a self-hosted application to the Internet
  6. Cloudflare Access policies
  7. One-time PIN login
  8. Cloudflare Tunnel common errors
  9. Troubleshoot HTTPS origins with Cloudflare Tunnel
  10. Troubleshoot Cloudflare Access

Originally published . About our editorial updates.

Your next project deserves a better foundation.

Explore hosting built for your next chapter.

Explore hosting