In previous posts, I have run containers in my Ampere Linux VM with an endpoint that accepts only HTTP traffic. To access it security from outside OCI, I have demonstrated these steps: open both OCI and OS level firewalls for the host port the container mapped to, configure reverse proxy to handle incoming traffic and setup certificates for HTTPS traffic.
Althought nginx with Let’s Encrypt is the industry-standard method for hosting a robust, public-facing website or API, there are other alternatives. In this post, I will show how you can use a Cloudflare Tunnel (cloudflared) to create a secure, outbound connection from your private resources directly to the nearest Cloudflare data center, all without exposing your internal or VM ports.
Cloudflare Tunnel benefits
- Bypass Firewall Rules: It establishes an outbound connection from the server to Cloudflare, so incoming firewall doesn’t need to allow external IPs.
- Handle secure traffic automatically: It allows a client to call an
https://URL, and the Tunnel terminates the SSL and sends the request to our VM server over plainhttp://localhost:port.
- no public IP needed: the container can continue to run on its private IP (e.g.,
127.0.0.1:7775). Thecloudflaredagent runs on the same machine and creates a secure, outbound connection to Cloudflare.
Here’s a comparison between Cloudflare Tunnel & NGINX + Let’s Encrypt
| Feature | Cloudflare Tunnel (cloudflared) |
NGINX + Let’s Encrypt |
|---|---|---|
| Recommended For | APIs, backends, and internal services that require secure access. | Public websites, load balancing, full web hosting solutions. |
| Complexity | Low. Simple CLI commands to create host , skipping complex NGINX configuration and certificate renewal cycles. | High. Configuration files (proxy headers to pass the request correctly to the Llama server), Certbot, cron jobs for auto-renewal of cert |
| Security Model | Zero Trust (nothing is exposed to the public internet). No firewall configuration needed, as connection is outbound, so you don’t need to open any incoming ports (like 80, 443, or 7775) on your server’s firewall. | Requires publicly exposing port 80 and port 443 (HTTPS) (inbound) on your server’s host firewall and any upstream network security groups. |
| HTTPS Protocol | container continues to run on http://localhost:7775. The Tunnel handles the necessary HTTPS termination at the Cloudflare edge. |
Manual SSL setup: installing NGINX, configuring a server block, installing Certbot/Let’s Encrypt, and setting up a cron job for renewal (every 90 days). |
Setup Cloudflare Tunnel
note: To setup a Cloudflare Tunnel, make sure you have a Cloudflare account and a domain name managed by Cloudflare. It will be used in Step 4 below.
This process involves three main parts: installing the software on the server, configuring the Tunnel, and connecting it to a domain.
- Install
cloudflared: deamon on the same machine running the container. - Create a Tunnel: Authenticate
cloudflaredand create a named tunnel. - Configure DNS: Point a subdomain (e.g.,
llama.yourdomain.com) to the tunnel.
1: Install cloudflared
For Oracle Linux, 1. Add Cloudflare’s repository:
curl -fsSl https://pkg.cloudflare.com/cloudflared.repo | sudo tee /etc/yum.repos.d/cloudflared.repo
- Update repositories and install cloudflared:
sudo yum update && sudo yum install cloudflared
For Ubuntu: 1. Add Cloudflare’s package signing key. This key is used to verify the authenticity of the packages you download.
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-public-v2.gpg | sudo tee /usr/share/keyrings/cloudflare-public-v2.gpg >/dev/null
- Add Cloudflare package repository to your system’s sources list:
echo "deb [signed-by=/usr/share/keyrings/cloudflare-public-v2.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
Note: The any placeholder generally works for most Ubuntu versions. If you have an issue, you might need to replace any with your specific distribution’s codename (e.g., jammy for 22.04 or focal for 20.04).
- Update your package list and install cloudflared:
sudo apt-get update && sudo apt-get install cloudflared
- Check that cloudflared is installed and running by checking its version:
cloudflared -v
2. Authenticate
Run the following command. This will give you an URL. Since we’re running a headless Linux VM, copy and open the URL in your local computer. Log in to your Cloudflare account, select your domain, and Cloudflare will issue a certificate. Leave cloudflared running to download the cert automatically.
cloudflared tunnel loginOnce authenticated, you will see this in the terminal:
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
~/.cloudflared/cert.pemcloudflared has created a configuration file directory (e.g., ~/.cloudflared/) to store the certificate file (cert.pem), which is used to prove its identity to Cloudflare.
3: Create and Configure the Tunnel
Create a Tunnel:
Choose a name (e.g.,
llama-api) and create the tunnel.$cloudflared tunnel create llama-api Tunnel credentials written to ~/.cloudflared/your-tunnel-id.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel. Created tunnel llama-api with id <your-tunnel-id>It will output a Tunnel ID and creates a credentials file.
Create the Configuration File: Use a text editor like nano or vi to create the configuration file, named
config.ymlinside your~/.cloudflared/directory (the same location as thecert.pemfile). This tells the Tunnel where to send incoming requests.Confirm the following values:
- Replace
<your-tunnel-id>with the ID from the previous step. - Replace
~/.cloudflared/with the location output in the previous step:Tunnel credentials written to ~/.cloudflared/your-tunnel-id.json. - Replace
hostnamewith your own subdomain, andserviceto your local container or server’s port inside the VM host.
sudo nano config.yml# ~/.cloudflared/config.yml tunnel: <your-tunnel-id> # Path to the credentials file created by 'tunnel create' credentials-file: ~/.cloudflared/<your-tunnel-id>.json # Define the routing rules for the tunnel ingress: # 1. Point your chosen hostname to your container's port - hostname: llama.yourdomain.com service: http://localhost:7775 # 2. Fallback rule: required to catch everything else and avoid errors - service: http_status:404Note: Since the
cloudflaredservice is running on the host network, http://localhost:7775 will correctly route to your container’s port 7775 if the container’s port is published to the host (docker run -p 7775:7775).- Replace
4: Connect the Tunnel to Cloudflare DNS
This step tells Cloudflare DNS to route traffic for your chosen public hostname to your Tunnel.
$cloudflared tunnel route dns llama-api llama.yourdomain.com
INF Added CNAME llama.yourdomain.com which will route to this tunnel tunnelID=<your-tunnel-id>This automatically creates a CNAME record in your Cloudflare DNS that points llama.yourdomain.com to the Tunnel.
5: Run the Tunnel Service
Start the tunnel using the configuration file you created.
cloudflared tunnel run llama-apiThe terminal should now show log output indicating the Tunnel is active and connected. We can now access the private container via the public URL https://llama.yourdomain.com, secured by Cloudflare.
6: Keep Cloudflared running
So far, we have successfully tunneled external traffic to our local server. However, if our SSH session terminates or the VM is reboot, cloudflared will stop and we will lose the secure connection. To keep cloudflared running, we have to install it as a system service via systemd service as follows:
Install the service:
sudo cloudflared --config ~/.cloudflared/config.yml service install- This creates a service file and moves the config to
/etc/cloudflared/config.yml.
- This creates a service file and moves the config to
Start and check the service:
sudo systemctl start cloudflared sudo systemctl status cloudflared