This guide details how to secure your self-hosted Coolify instance on Hetzner with Tailscale for private network access and Cloudflare for secure DNS resolution and SSL certificates.
Prerequisites:
- A running Coolify instance on a Hetzner VM (or any VPS provider)
- A Cloudflare account with your domain managed
Steps:
- Setting Up Tailscale:
- If you haven't already, follow the guide from Arcjet blog (https://blog.arcjet.com/protecting-self-hosted-coolify-apps/) to set up Tailscale on your server. This will provide you with a private Tailscale IP address for secure access.
- Harden Your Server (Hetzner Specific):
- While the guide above recommends UFW for firewall configuration, Hetzner allows firewall settings directly through their control panel. Use these settings to restrict incoming traffic to only the necessary ports for Tailscale (41641 UDP and 3478 UDP).
- Update DNS Record:
- Once Tailscale is set up, log in to your Cloudflare dashboard and update your domain's A record. Change it from pointing to your public IP address to the Tailscale private IP address you obtained in step 1 (e.g., A * 100.96.15.16).
N.B. Notice that after you do this you will only have access to coolify dashboard through the tailscale-private-ip:8000 and no longer access from your custom domain nor your public ip.
- Configure Coolify Proxy (Using Traefik):
- Since your public IP is no longer accessible, Coolify needs a proxy to handle external connections. This guide will use Traefik for this purpose.
- Generate Cloudflare API Token:
- In your Cloudflare dashboard, navigate to Profile > API Tokens and create an API token with "Edit zone DNS" permission.
- Configure Traefik Docker Compose:
- Edit the Coolify docker-compose file and add the following configuration for the Traefik service:
- Environment Variables:
CF_API_EMAIL=your_email@email.com
(your Cloudflare email)CF_DNS_API_TOKEN=your_api_token
(the API token you generated)- Command:
- Update the command section with the following additions:
-certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
-certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0
- Replace
-certificatesresolvers.letsencrypt.acme.httpchallenge=true
with-certificatesresolvers.letsencrypt.acme.httpchallenge=false
(commented out) - This is how your docker compose file should look like
networks: coolify: external: true services: traefik: container_name: coolify-proxy image: 'traefik:v3.1' restart: unless-stopped extra_hosts: - 'host.docker.internal:host-gateway' environment: - CF_API_EMAIL=xxx - CF_DNS_API_TOKEN=xxx networks: - coolify ports: - '80:80' - '443:443' - '443:443/udp' - '8080:8080' healthcheck: test: 'wget -qO- http://localhost:80/ping || exit 1' interval: 4s timeout: 2s retries: 5 volumes: - '/var/run/docker.sock:/var/run/docker.sock:ro' - '/data/coolify/proxy:/traefik' command: - '--ping=true' - '--ping.entrypoint=http' - '--api.dashboard=true' - '--api.insecure=false' - '--entrypoints.http.address=:80' - '--entrypoints.https.address=:443' - '--entrypoints.http.http.encodequerysemicolons=true' - '--entryPoints.http.http2.maxConcurrentStreams=50' - '--entrypoints.https.http.encodequerysemicolons=true' - '--entryPoints.https.http2.maxConcurrentStreams=50' - '--entrypoints.https.http3' - '--providers.docker.exposedbydefault=false' - '--providers.file.directory=/traefik/dynamic/' - '--providers.file.watch=true' - '--certificatesresolvers.letsencrypt.acme.httpchallenge=false' - '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json' - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http' - '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare' - '--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0' - '--providers.docker=true' labels: - traefik.enable=true - traefik.http.routers.traefik.entrypoints=http - traefik.http.routers.traefik.service=api@internal - traefik.http.services.traefik.loadbalancer.server.port=8080 - coolify.managed=true - coolify.proxy=true
- Restart Coolify Services:
- After updating the docker-compose file, click on save and then restart service with the new Traefik configuration.
Congratulations you now have a VM running privately on your tailscale network with SSL certificates.
Benefits:
- Enhanced Security: This setup restricts access to your Coolify instance through the Tailscale private network, adding an extra layer of security.
- Automatic SSL Certificates: Traefik leverages Cloudflare for automatic SSL certificate generation and renewal, ensuring secure HTTPS connections.
Additional Notes:
- This guide focuses on Traefik as the proxy solution. You can explore alternative options like Caddy if preferred.
- Refer to the provided resource links for detailed information on specific configurations.
Next steps:
- Continue on the tutorial provided by coolify to use a single certificates for all the instances that you use.
Sources: