Goal: expose two hostnames via Cloudflare Tunnel that route to services running on a single EC2 instance:
<SUBDOMAIN 1>.<YOUR DOMAIN NAME>.com→http://localhost:80(Python website)<SUBDOMAIN 2>.<YOUR DOMAIN NAME>.com→http://localhost:3000(Express API)
This guide is concise and written for Amazon Linux (YUM/RPM). Commands assume you run them as ec2-user with sudo where necessary.
- Install
cloudflaredon the EC2 instance. - Authenticate
cloudflaredto your Cloudflare account. - Create a named tunnel and save its credentials.
- Create a
config.ymlwithingressrules for:80and:3000. - Point DNS (CNAME) records for your subdomains to the tunnel.
- Install
cloudflaredas a system service and start it. - Test and debug.
- EC2 instance running Amazon Linux (user:
ec2-user). - The website is listening on localhost:80; Express API on localhost:3000.
- You own the Cloudflare zone for
<YOUR DOMAIN NAME>and can add DNS records and create tunnels in the Cloudflare Zero Trust / Tunnels section. curl,sudo, andsystemdare available (default on Amazon Linux).
On Amazon Linux this guide defaults to the binary install which works reliably.
# download binary and install
cd /home/ec2-user
curl -LO "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64"
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
sudo chmod +x /usr/local/bin/cloudflared
# verify
cloudflared --versionIf you prefer, you can also install via a package manager on other distros — but for Amazon Linux the binary above is the recommended approach.
Run the login command which opens an authentication URL (you may need to run from a machine with a browser). On a headless EC2, it prints a URL to open on your laptop.
cloudflared tunnel loginThis creates a cert.pem in ~/.cloudflared/ and allows cloudflared to create tunnels in your Cloudflare account.
Choose a name (e.g. my-cf-tunnel). Creating a tunnel produces a credentials file (JSON) stored under ~/.cloudflared/ and prints its UUID.
cloudflared tunnel create my-cf-tunnel
# Example output -> Tunnel credentials written to /home/ec2-user/.cloudflared/<UUID>.json
# Note the UUID printed by the command (you'll use it in DNS or config)Make a note of the UUID. You can list tunnels anytime:
cloudflared tunnel listCreate the Cloudflared config directory and the config file:
mkdir -p /home/ec2-user/.cloudflared
sudo chown ec2-user:ec2-user /home/ec2-user/.cloudflaredCreate /home/ec2-user/.cloudflared/config.yml with the following contents (replace <UUID> with your tunnel UUID):
# /home/ec2-user/.cloudflared/config.yml
tunnel: <UUID>
credentials-file: /home/ec2-user/.cloudflared/<UUID>.json
originRequest:
# if your local site uses self-signed certs or you intentionally want Cloudflare not to verify
noTLSVerify: true
ingress:
- hostname: <SUBDOMAIN 1>.<YOUR DOMAIN NAME>.com
service: http://localhost:80
- hostname: <SUBDOMAIN 2>.<YOUR DOMAIN NAME>.com
service: http://localhost:3000
- service: http_status:404Notes:
noTLSVerify: trueis optional. If your origin serves proper TLS certs, remove or set tofalse.- Keep file permissions safe:
chmod 600 /home/ec2-user/.cloudflared/<UUID>.json.
There are two ways:
A. Let cloudflared create DNS records for you (recommended if you used cloudflared tunnel login and have the cert installed):
# create CNAME records in Cloudflare DNS for each hostname
cloudflared tunnel route dns <UUID or NAME> <SUBDOMAIN 1>.<YOUR DOMAIN NAME>.com
cloudflared tunnel route dns <UUID or NAME> <SUBDOMAIN 2>.<YOUR DOMAIN NAME>.comB. Manually in the Cloudflare Dashboard
-
Go to the DNS settings for
<YOUR DOMAIN>. -
Add two CNAME records:
- Name:
<SUBDOMAIN 1>→ Target:<UUID>.cfargotunnel.com - Name:
<SUBDOMAIN 2>→ Target:<UUID>.cfargotunnel.com
- Name:
-
Save.
Important: the DNS target must be <UUID>.cfargotunnel.com (the tunnel subdomain). If you use cloudflared tunnel route dns the command will create the records for you.
To avoid permission/config path issues when running cloudflared as a system service, copy the tunnel credentials and config into a system-level location and point the service at that location.
# create system config dir and copy files
sudo ls /usr/local/etc || true
sudo mkdir -p /usr/local/etc/cloudflared
sudo cp /home/ec2-user/.cloudflared/*.json /usr/local/etc/cloudflared/
sudo cp /home/ec2-user/.cloudflared/*.yml /usr/local/etc/cloudflared/
sudo chown -R root:root /usr/local/etc/cloudflared
sudo chmod 640 /usr/local/etc/cloudflared/*.json
# Create a systemd unit that references the system config
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl start cloudflared
# check status
sudo systemctl status cloudflared
journalctl -u cloudflared -fIf you still prefer to run the service as ec2-user, adjust the User= line and ensure /usr/local/etc/cloudflared has appropriate ownership and read permissions for that user.
- Ensure
cloudflaredis running:
sudo systemctl status cloudflared
journalctl -u cloudflared -f- Test the tunnel routing from your laptop/browser: visit
https://<SUBDOMAIN 1>.<YOUR DOMAIN NAME>.comandhttps://<SUBDOMAIN 2>.<YOUR DOMAIN NAME>.com. - Locally on the EC2 instance, you can check which ingress rule matches:
cloudflared tunnel ingress rule https://<SUBDOMAIN 1>.<YOUR DOMAIN NAME>.com
cloudflared tunnel ingress rule https://<SUBDOMAIN 2>.<YOUR DOMAIN NAME>.com- When DNS is newly created, allow some minutes for propagation.
- Check cloudflared logs:
journalctl -u cloudflared -n 200 --no-pager. - Confirm the tunnel is connected with:
cloudflared tunnel list. - Confirm Cloudflare DNS CNAMEs point to
<UUID>.cfargotunnel.com. - If you see HTTP 1016 or 502 errors, the tunnel may be down or the
ingressrule doesn't match; check config.yml syntax and hostnames. - If you use
noTLSVerify: trueit avoids origin TLS validation problems — only use it when appropriate.
- Cloudflare still fronts TLS termination; you get HTTPS between client and Cloudflare. You can still enable Access policies or firewall rules in Cloudflare Zero Trust for extra protection.
- Don't expose administrative or internal-only endpoints via public hostnames unless protected by Access policies.
Use the system-level path so the systemd service can find the files. Replace <UUID> with your tunnel UUID.
# /usr/local/etc/cloudflared/config.yml
tunnel: <UUID>
credentials-file: /usr/local/etc/cloudflared/<UUID>.json
originRequest:
noTLSVerify: true
ingress:
- hostname: <SUBDOMAIN 1>.<YOUR DOMAIN NAME>.com
service: http://localhost:80
- hostname: <SUBDOMAIN 2>.<YOUR DOMAIN NAME>.com
service: http://localhost:3000
- service: http_status:404After you place the config.yml and credential JSON in /usr/local/etc/cloudflared/, make sure the service is reloaded and started (sudo systemctl daemon-reload && sudo systemctl restart cloudflared).