Web Analytics Made Easy - Statcounter

Secure Website Hosting Without Open Ports: Cloudflare Tunnel & Kamal-deploy

Hosting a website traditionally involves exposing certain ports ( 80, 443, ...) to the public internet. However, this approach often brings security risk, such as DDoS attacks and unauthorized access attempts. Or, simply put, there may be other reasons why you don’t want or can’t expose ports to the internet.

In this post, I’ll guide you through an alternative method to host your website securely without opening 80/443 ports. We’ll be using Cloudflare Tunnel, a service that connects your server to Cloudflare’s network securely, and Kamal-deploy, a deployment tool that simplifies the process of managing applications. By the end, you’ll have a fully deployed, secure website that doesn’t rely on open ports.

Let consider the diagram below first:

Secure Website Hosting Without Open Ports: Cloudflare Tunnel & Kamal
Diagram Secure Website Hosting Without Open Ports: Cloudflare Tunnel & Kamal

Components

Cloudflare Tunnel is the key component in this setup. It creates a secure tunnel that allows public access to your web service without the need to open any ports.

Image from: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/

Kamal-deploy simplifies the deployment process, allowing you to deploy applications to any Linux server in just a few minutes. It automates key tasks such as pulling code, building and running containers, and managing environment variables, networking, and SSL certificates.

Image from: https://semaphoreci.com/blog/mrsk

Prerequisite

  • A domain configurate to manage on Cloudflare, you can follow the guide here
  • A  linux server that pre-setup docker and allow to access from your local machine
  • Kamal already install on your project (assume that a Rails project, but you can use with any application that can be containerized.

Setup a Cloudflare tunnel

Setup an tunnel to your linux server, follow the official guide from Cloudflare  https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/

Copy full token and put in in safeplace, we'll use it to config on kamal

then, add new tunnel config to map domain into this tunnel.

Setup Kamal Deploy & Config application

the content of deploy.yml as the code bellow:

service: miningboard

ssh:
  user: ubuntu

image: private-registry/miningboard.com

servers:
  web:
    hosts:
      - 136.20.33.21
    labels:
      traefik.enable: true
      traefik.http.routers.miningboard-web.rule: Host(`miningboard.com`)
      traefik.http.routers.miningboard-web.entrypoints: web
    options:
      network: "pff"
# Credentials for your image host.
registry:
  server: registry.gitlab.com
  username:
    - DOCKER_REGISTRY_USERNAME
  password:
    - DOCKER_REGISTRY_TOKEN

env:
  clear:
    RAILS_LOG_TO_STDOUT: 1
    PORT: 3000
    HOST_PRIVATE_IP: 136.20.33.21
  secret:
    - RAILS_MASTER_KEY
    - RAILS_ENV

builder:
  secrets:
    - RAILS_MASTER_KEY

# Use accessory services (secrets come from .env).
accessories:
  cloudflared:
    image: cloudflare/cloudflared:2024.6.0
    host: 136.20.33.21
    env:
      secret:
        - TUNNEL_TOKEN
    cmd: tunnel run
    options:
      network: "pff"

# Configure custom arguments for Traefik
traefik:
  options:
    network: "pff"
  args:
    accesslog: true
    entryPoints.web.address: ":80"
  labels:
    traefik.enable: true
# Configure a custom healthcheck (default is /up on port 3000)
healthcheck:
  path: /up
  port: 3000

asset_path: /rails/public/assets
primary_role: web
hkamal: deploy.yml

In this Kamal deployment configuration, the Cloudflared Docker container is set up as an accessory of Kamal and connected to an external Docker network named "pff". This explains why, in the earlier tunnel configuration, we were able to point the domain to traefik:80.

If you don’t use an external network configuration, you would need to expose the Traefik port and change the tunnel config to point the domain to host_ip:<traefik_host_port>

Config the tunnel token in .env.rb

RAILS_ENV=production
TUNNEL_TOKEN=put/the/cloudflare/tunnel/token/here
# Other variables
# ....

.env.rb

Config/environments/production.rb

Update assume_ssl = true && force_ssl = false  to allow application can access with Cloudflare SSL/TLS Flexible mode, ssl terminating buy Cloudflare/& Cloudlfare tunnel

  # Assume all access to the app is happening through a SSL-terminating reverse proxy.
  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
  config.assume_ssl = true

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = false

Deploy with kamal

kamal envify
kamal env push
kamal traefik boot
kamal accessory boot cloudflared 
kamal deploy
kamal traefik reboot -y

Output

Here’s my hobby project: hosting on my local miniPC (Asus x300) using Cloudflare Tunnel:: https://miningboard.com/

https://miningboard.com/