all systems operationalIndia

Host a website from your own machine with Cloudflare Tunnel

Move your domain to Cloudflare, create a tunnel, and serve apps running on your own hardware without opening a single port.

Some projects are too heavy for a cheap VPS: ML models, data pipelines, search clusters, anything that needs a lot of RAM, a GPU, or CPU time. You might already have a machine at home that can run them. The problem is getting the internet to it safely.

The usual answer is to open ports on your router, find a static IP, and manage TLS certificates yourself. Cloudflare Tunnel skips all of that. A small program called cloudflared runs on your machine and makes an outbound connection to Cloudflare. Visitors hit Cloudflare, and Cloudflare sends their requests down that tunnel to your local app.

visitor ──https──▶ Cloudflare edge ──tunnel──▶ cloudflared (your machine) ──▶ http://localhost:3000

What you get:

  • No open ports. Your router stays closed. The connection starts from inside your network.
  • No static IP needed. It works behind CGNAT, a dynamic IP, even mobile hotspots.
  • Your home IP stays hidden. DNS points at Cloudflare, never at you.
  • Free HTTPS. Cloudflare issues and renews the certificate.

This guide goes from "I bought a domain" to "my app is live on it", step by step.

What you need

  • A domain name from any registrar (Namecheap, GoDaddy, Porkbun, …).
  • A free Cloudflare account.
  • A Linux machine (Debian/Ubuntu commands below) that stays on and runs your app on a local port, e.g. a web app on localhost:3000.
  • sudo access on that machine.

Throughout this post, replace example.com with your domain and home-server with whatever you want to call your tunnel.

Step 1: Move your domain's DNS to Cloudflare

You don't have to move the registration to use Cloudflare. You only need Cloudflare to answer DNS for the domain, which you do by changing its nameservers. Your registrar still bills you for renewals as before.

  1. In the Cloudflare dashboard, click Add a domain, enter example.com, and pick the Free plan.
  2. Cloudflare scans your existing DNS records and imports them. Review the list. Anything you rely on today, especially email (MX and TXT records), must be there before you switch.
  3. Cloudflare shows you two nameservers, something like name1.ns.cloudflare.com and name2.ns.cloudflare.com.
  4. If DNSSEC is on at your registrar, turn it off first. Leaving it on while the nameservers change can take the domain offline. You can turn it back on later from Cloudflare's DNS settings.
  5. At your registrar, find the domain's nameserver setting and switch it to custom nameservers. On Namecheap that's Domain List → Manage → Nameservers → Custom DNS. Other registrars have an equivalent screen. Paste in Cloudflare's two nameservers and save.
  6. Wait. Cloudflare emails you when the domain becomes Active. It's often done within an hour, but can take up to 24.

Check from any terminal:

dig NS example.com +short
# name1.ns.cloudflare.com.
# name2.ns.cloudflare.com.

When both answers are Cloudflare's, you're in.

Optional: transfer the registration to Cloudflare Registrar

With the nameservers moved, you can also move the registration itself. Cloudflare Registrar charges the wholesale price, with no markup, and includes WHOIS privacy. It's optional; everything else in this guide works either way.

  1. The domain must already be Active on Cloudflare (step 1).
  2. At your current registrar, unlock the domain and request the authorisation (EPP) code.
  3. In Cloudflare, go to Domain Registration → Transfer Domains, select the domain, and enter the code.
  4. Pay for the transfer. It adds one year to your registration, so nothing is wasted.
  5. Approve the confirmation email from your old registrar, or wait for it to time out, which usually takes up to 5 days.

Two rules to know:

  • The 60-day lock. You can't transfer a domain within 60 days of registering it or of a previous transfer.
  • TLD support. Not every top-level domain can be transferred to Cloudflare. The transfer page tells you if yours can't.

Step 2: Tidy up DNS

Open DNS → Records for the domain.

  • Keep your email records. If you use email forwarding or a mail provider, leave its MX and TXT (SPF, DKIM) records alone. They should stay DNS only (grey cloud).
  • Delete stale NS records. Imports sometimes bring in NS records pointing at your old registrar's DNS servers. They do nothing useful inside a Cloudflare zone and can confuse tools, so remove them.
  • Don't create A records for your home IP. The tunnel creates the records you need in step 4. Pointing DNS at your home IP would expose it, which is exactly what the tunnel avoids.

Step 3: Install cloudflared

On the machine that runs your app, add Cloudflare's package repository and install:

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
sudo apt-get update && sudo apt-get install -y cloudflared

Or grab the .deb directly:

curl -L -o cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb

Check it:

cloudflared --version

The apt route is better long term, because apt upgrade keeps cloudflared current.

Step 4: Create the tunnel

Log in

cloudflared tunnel login

This prints a URL. Open it in a browser, log in to Cloudflare, and pick your domain. cloudflared saves a certificate to ~/.cloudflared/cert.pem, which lets it manage tunnels and DNS for that domain.

Create it

cloudflared tunnel create home-server

This registers the tunnel with Cloudflare and writes a credentials file to ~/.cloudflared/<TUNNEL-UUID>.json. Treat that file like a password: anyone who has it can run your tunnel. Never commit it to git.

Confirm it exists:

cloudflared tunnel list

Point your hostnames at it

cloudflared tunnel route dns home-server example.com
cloudflared tunnel route dns home-server www.example.com

Each command creates a proxied CNAME to <TUNNEL-UUID>.cfargotunnel.com. In the dashboard these show up with the type Tunnel and the tunnel's name. Pointing the bare domain (example.com) at a CNAME is normally not allowed; Cloudflare handles it with CNAME flattening, so it just works.

Add a line for every hostname you want to serve, for example api.example.com.

Step 5: Tell the tunnel where to send traffic

The ingress rules in a config file map incoming hostnames and paths to local services. Create ~/.cloudflared/config.yml:

tunnel: <TUNNEL-UUID>
credentials-file: /home/you/.cloudflared/<TUNNEL-UUID>.json

ingress:
  # API requests on the main domain go to a separate backend
  - hostname: example.com
    path: ^/api/
    service: http://localhost:8080

  # Everything else on the main domain and www goes to the website
  - hostname: example.com
    service: http://localhost:3000
  - hostname: www.example.com
    service: http://localhost:3000

  # A whole subdomain for another app
  - hostname: app.example.com
    service: http://localhost:9000

  # Required catch-all: anything that matched nothing above
  - service: http_status:404

How the rules work:

  • Top to bottom, first match wins. Put specific rules (with a path) above general ones for the same hostname.

  • path is a regular expression, not a glob. ^/api/ matches paths that start with /api/. Without the ^ it would match /api/ anywhere in the path.

  • The last rule must have no hostname. It catches everything else. http_status:404 is the usual choice.

  • service can be any local URL: a different port, a different machine on your LAN, or https:// for a local service with its own certificate.

  • If a local service uses a self-signed HTTPS certificate, add this under that rule:

    originRequest:
      noTLSVerify: true
    

    Only use it for local self-signed certificates. Plain http://localhost doesn't need it.

Check your rules before running anything:

cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://example.com/api/users   # which rule catches this URL?

Then test in the foreground:

cloudflared tunnel run home-server

Open https://example.com. If your app loads, the tunnel works. Stop it with Ctrl+C and make it permanent.

Step 6: Run it as a service

As a systemd service, the tunnel starts on boot and restarts if it crashes. The service reads its config from /etc/cloudflared/, so copy the files there first:

sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/config.yml /etc/cloudflared/config.yml
sudo cp ~/.cloudflared/<TUNNEL-UUID>.json /etc/cloudflared/

Edit /etc/cloudflared/config.yml so credentials-file points at the new location:

credentials-file: /etc/cloudflared/<TUNNEL-UUID>.json

Lock the files down, then install and start the service:

sudo chmod 600 /etc/cloudflared/config.yml /etc/cloudflared/<TUNNEL-UUID>.json
sudo cloudflared service install
sudo systemctl enable --now cloudflared

Check that it's healthy:

systemctl status cloudflared          # should say "active (running)"
cloudflared tunnel info home-server   # should list active connections
journalctl -u cloudflared -f          # live logs

After any change to /etc/cloudflared/config.yml, run sudo systemctl restart cloudflared.

Step 7: Verify and harden

Check that traffic really flows through Cloudflare:

curl -sI https://example.com | grep -i '^server'
# server: cloudflare

Then a few habits that keep a home server safe:

  • Only route what you mean to publish. Every ingress rule is a door to the internet. Never point a hostname at a database admin panel, a dashboard, or anything else meant only for you. When you add or change a rule, double-check which local service that port actually belongs to.
  • Put private tools behind Cloudflare Access. For internal dashboards you still want to reach from outside, create an Access application under Zero Trust → Access. Visitors must then log in with an email code or SSO before the request ever reaches your machine.
  • Bind local services to 127.0.0.1 where you can. The tunnel reaches them fine, and nothing else on your network can.
  • Turn on "Always Use HTTPS" under SSL/TLS → Edge Certificates.
  • Keep cloudflared updated with apt upgrade.

Troubleshooting

SymptomLikely causeFix
Error 1033The tunnel isn't connectedsystemctl status cloudflared; check the logs
502 Bad Gatewaycloudflared is up, but nothing answers on the local portIs the app running? Is the port in config.yml correct? Try curl localhost:3000 on the machine
404 from your site's domainNo ingress rule matchedcloudflared tunnel ingress rule https://… shows which rule a URL hits
Domain doesn't resolveNameservers not active yetdig NS example.com +short; wait, or re-check the registrar settings
TLS errors to a local https:// serviceSelf-signed certificateAdd noTLSVerify: true for that rule only

Recap

  1. Point your domain's nameservers at Cloudflare (and optionally transfer the registration).
  2. Clean up DNS: keep email records, drop stale ones, and never expose your home IP.
  3. Install cloudflared, then tunnel login → tunnel create → tunnel route dns.
  4. Map hostnames and paths to local ports in config.yml, ending with a 404 catch-all.
  5. Run it as a systemd service, verify it, and only expose what you mean to.

Your home machine now serves the internet over HTTPS, with no open ports and no public IP, and it can run workloads far heavier than a small VPS could afford.

Connected

shares a topic with this post