May 29, 2025

How I Secured my Self-hosted Services From Public Access

My journey to secure my Coolify panel was a dive into the practical side of Zero Trust. While server-level firewalls and direct reverse proxy configurations have their place, Cloudflare Access provided an elegant and powerful solution for restricting application access without major headaches.

How I Secured my Self-hosted Services From Public Access

Self-hosting is an incredibly empowering journey. With tools like Coolify, spinning up applications, databases, and services on your own VPS has become remarkably accessible. I was thrilled with how quickly I got my Coolify panel up and running at xxxxx.sethonne.dev, ready to manage all my private projects.

But then came the "uh-oh" moment. My Coolify panel, the central command for all my self-hosted applications, with root SSH access, was sitting there, publicly accessible on the internet. Anyone could navigate to the URL and see the login page. While Coolify itself is secure, exposing any management interface unnecessarily is a risk I wasn't comfortable with. My goal was clear: restrict access to only myself, and apply this same protection to other non-public applications hosted via Coolify, like yyyyy.sethonne.dev.

The Open Door: The Initial Problem

The core issue was simple: xxxxx.sethonne.dev was wide open. While it required a login, the login page itself was visible to the world. This isn't ideal for a critical management tool. I wanted to make it "dark" to the public internet, visible and accessible only when I was connected through a secure, private channel – my VPN running on a separate VPS. This same principle needed to apply to other sensitive internal tools I planned to host.

Weighing All My Options

I brainstormed several ways to lock things down:

  1. Server-Level Firewall (UFW/iptables): My first thought was to go direct. Configure the VPS firewall to only allow incoming connections on the web ports (80, 443) and SSH from my VPN's static IP address.
    • Pros: Direct, very effective at the network level.
    • Cons: Relies on my VPN having a truly static IP. If it ever changed, I'd be locked out. It felt a bit rigid for managing access to multiple web applications.
  2. Reverse Proxy Configuration (Traefik/Caddy/Nginx): Coolify uses a reverse proxy internally. I considered adding IP whitelisting rules directly into its configuration.
    • Cons: This seemed like it could get complex, potentially fighting with Coolify's own management of its internal proxy. I wanted a solution that wasn't too intrusive to the Coolify setup itself.
  3. Cloudflare Access / Tunnels (or similar like Tailscale): This route seemed promising.
    • Cloudflare Access: Use Cloudflare's Zero Trust policies to define who can reach specific applications based on IP, email, identity provider, etc.
    • Cloudflare Tunnel: Create a secure, outbound-only connection from my VPS to Cloudflare, meaning I wouldn't even need to open inbound ports (80, 443) on my VPS firewall to the public internet. This could be combined with Access policies.
    • Tailscale: Create a private overlay network. A good option, but I was already using Cloudflare for DNS and wanted to explore their integrated solutions first.

The Cloudflare Zero Trust approach, specifically using Access policies, quickly became the front-runner. It offered a good balance of robust security, flexibility, and manageability without needing deep surgery on my Coolify server's firewall or proxy configs for every app.

Enter Cloudflare Zero Trust

Cloudflare Zero Trust works on a simple but powerful premise: never trust, always verify. Instead of relying solely on network location (like an IP whitelist on my server), it authenticates every request at Cloudflare's edge before it ever gets a chance to hit my origin server. This meant I could make my Coolify panel (and other apps) invisible to the public internet, yet easily accessible to me.

Here’s how I set it up (note that my domain is registered under Cloudflare):

Step 1: Ensuring DNS is Cloudflare-Proxied (The Orange Cloud)

This is fundamental. For Cloudflare Access to protect an application, the DNS record for that application (e.g., xxxxx.sethonne.dev) must be proxied through Cloudflare. This means clicking that little cloud icon in the Cloudflare DNS settings until it's orange. This routes all traffic through Cloudflare's network first.

Step 2: Defining the "Application" in Cloudflare Zero Trust

Over in the Cloudflare Zero Trust dashboard (usually dash.teams.cloudflare.com):

  1. Navigate to Access -> Applications.
  2. Add an application: I chose "Self-hosted."
  3. Application Configuration:
    • Application Name: A friendly name, e.g., "Coolify Panel" or "Private Admin Panels."
    • Session Duration: How long an authenticated session remains valid.
      • My Recommendation for Coolify Panel: I initially had it at 12 hours, but for sensitive admin panels, 1 to 4 hours is much better. Shorter means re-authenticating more often, but it's more secure.
      • For other private apps: This can be longer (4-24 hours) depending on their sensitivity.
    • Identity Providers: Cloudflare allows various IdPs. I could use email OTP, GitHub, Google, etc. For pure IP whitelisting, this is less critical upfront but good for backup access.
    • Public Hostname Configuration (Crucial):
      • Subdomain: xxxxx
      • Domain: sethonne.dev
      • Path: Left blank to protect the entire xxxxx.sethonne.dev subdomain.
      • Pro Tip: You can add multiple subdomains here (e.g., yyyyy.sethonne.dev, zzzzz.sethonne.dev) to protect them all under the same "application" definition and share policies. Wildcards like *.private.sethonne.dev are also an option if you structure your hostnames accordingly.

Step 3: Crafting the Access Policy (The Gatekeeper)

Once the application is defined, you create policies to specify who can access it. This is where the magic happens.

  1. Policy Name: Something descriptive, like "Allow VPN IP."
  2. Action: "Allow."
  3. Configure Rules: This is the core logic.
    • Rule 1: IP Whitelisting.
      • Selector: IP Source
      • Operator: is
      • Value: YOUR_VPN_IP_ADDRESS
      • Finding my VPN's Exit IP: I connected to my VPN (which is hosted on its own VPS). Then, I simply went to a site like whatismyip.com. The IP address shown there is the public IP of my VPS. This is the IP Cloudflare will see when I access xxxxx.sethonne.dev through the VPN, and it's the one I whitelisted.
      • Important Note for Self-Hosted VPNs on the Same Server: If your VPN is running on the same VPS as Coolify, the "VPN IP" will actually be your Coolify VPS's own public IP. This sounds counterintuitive, but it's correct. Cloudflare sees the request originating from your server's IP when you access it via the VPN tunnel that terminates on that same server. This isn't a security risk for this specific use case because Cloudflare Access is providing the authentication layer before the request even gets to your server application.
    • Rule 2 (Optional but Recommended Backup): Email Authentication.
      • Selector: Email
      • Operator: is
      • Value: [email protected]
      • This requires you to log in via an Identity Provider configured in Zero Trust (like a one-time PIN sent to your email, or via Google/GitHub). It’s a great backup if your VPN IP changes unexpectedly or you need access from a different network. I set my policy to require EITHER the IP match OR the email authentication.

How Cloudflare Zero Trust Protects "Under the Hood"

With this setup:

  1. My DNS for xxxxx.sethonne.dev points to Cloudflare (it's proxied).
  2. When I (or anyone) tries to access xxxxx.sethonne.dev, the request hits Cloudflare's edge network first, not my origin server.
  3. Cloudflare Access identifies it as a protected application and checks its policies.
  4. It verifies the request:
    • Is the source IP my whitelisted VPN IP?
    • (If I added it) Or, has the user authenticated via the specified email?
  5. If rules are met: Cloudflare forwards the request to my Coolify VPS.
  6. If rules are not met: Cloudflare blocks the request right at its edge. The request never even reaches my origin server.

My Coolify panel is now effectively "dark" to the public internet for that application.

The Sweet Relief: A Secure Panel and Peace of Mind

The difference was immediate. Trying to access xxxxx.sethonne.dev from any IP address not matching my VPN's exit IP (and without email authentication) resulted in a Cloudflare block page. But connect via my VPN, and the Coolify login page appeared as normal.

This same method can be applied to any other non-public web application I host via Coolify, simply by adding their subdomains to the Cloudflare Access application definition or creating new ones with similar policies.

Key Settings & Nuances for Cloudflare Access

  • Cloudflare Zero Trust Free Tier: The free tier is surprisingly generous. It allows up to 50 users. A "user" here refers to someone who actively authenticates through Zero Trust policies or uses the WARP client. It doesn't limit how many people can edit Cloudflare rules. For a solo user like me, or even a small team, the free tier is more than sufficient.
  • SSL/TLS Mode: For this to work securely, Cloudflare's SSL/TLS mode for the proxied domains should ideally be set to Full (Strict). This ensures an encrypted connection from the user to Cloudflare, and from Cloudflare to my origin server. Coolify typically manages SSL certificates for the services it hosts (via Traefik or Caddy), so this setting aligns perfectly.
  • Cloudflare Tunnel (Next Level): While Access Policies alone are a massive security win, combining them with Cloudflare Tunnel would mean I wouldn't need to open any inbound ports (like 80/443) on my VPS's firewall for these web apps. The tunnel creates a secure outbound connection from my server to Cloudflare. Definitely something to explore for even tighter security, but Access policies provide the immediate application-level lockdown I needed.

Conclusion

My journey to secure my Coolify panel was a dive into the practical side of Zero Trust. While server-level firewalls and direct reverse proxy configurations have their place, Cloudflare Access provided an elegant and powerful solution for restricting application access without major headaches.

Self-hosting is fantastic, but with great power comes the great responsibility of securing your setup. For my use case – protecting admin panels and private web applications on a Coolify instance – Cloudflare Zero Trust has proven to be an invaluable ally. It's a robust layer of protection that makes my self-hosted world a much safer place, all while being relatively straightforward to implement.