Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cloudflare

piqueld works with Cloudflare in two independent ways:

Before you start

Use Cloudflare as a DNS provider

Create an API token

piqueld calls Cloudflare’s API with an API token that has two permissions:

Permissionpiqueld uses it to
Zone › Zone › Read (Zone Read in the permissions reference)list your zones
Zone › DNS › Edit (DNS Write in the permissions reference)create, read, update and delete records

Limit the token’s zone resources to the zones piqueld should use, or allow them all. Cloudflare’s Create API token explains how to create it, and its API token permissions list each permission.

Set it up

  1. Save the token in a file that only root can read, such as an agenix or sops-nix secret.

  2. Add the provider to your configuration:

    services.piqueld.settings.dns.providers = [
      {
        kind = "cloudflare";
        api_token_file = "/run/secrets/cloudflare-dns-token";
        # Only to let piqueld manage routes' DNS records.
        manage_records = true;
      }
    ];
    

    In /etc/piqueld/config.toml, pass the file to the service with LoadCredential=, as for the tailnet auth key, and refer to it by name:

    [[dns.providers]]
    kind = "cloudflare"
    api_token_file = "cloudflare-dns-token"
    manage_records = true
    

    [[dns.providers]] lists every provider setting.

  3. Restart the daemon, which reads the token only when it starts. On NixOS, rebuild the system. Elsewhere, reload the unit you edited first:

    sudo systemctl daemon-reload
    sudo systemctl restart piqueld
    

Check that it works

Ask piqueld to list your zones now. In the dashboard, select Check DNS providers in System status: the DNS provider card shows the result. On the host, with piquelctl:

$ sudo piquelctl dns refresh
DNS provider: cloudflare (healthy, manages route records): example.com

It lists every zone the token can read. piqueld also lists them when it starts, and every hour. After you change the token’s permissions or add a zone, check again. Checking needs the system:operate permission, and seeing the result system:read.

Serve public routes through a Cloudflare Tunnel

Without a tunnel, the internet reaches your public routes on your server’s ports 80 and 443. With one, piqueld runs Cloudflare’s cloudflared beside the gateway. It connects out to Cloudflare, and Cloudflare sends your public routes’ requests back through that connection. Your server needs no inbound port at all, which also works behind NAT. Private routes still arrive over your tailnet.

Consider the trade-offs first:

  • Cloudflare sees all your public traffic unencrypted. It serves HTTPS to your visitors, and forwards their requests through the tunnel. On your server, the tunnel’s traffic stays on a network that only piqueld’s own containers join, so your applications can’t observe it.
  • Ports 80 and 443 close. Every public route goes through the tunnel; you can’t serve some on ports 80 and 443.
  • Cloudflare’s free Universal SSL certificate covers one level of subdomain. It covers example.com and notes.example.com, but not deeper names such as x.staging.example.com; see Cloudflare’s Universal SSL limitations.
  • The gateway no longer redirects HTTP to HTTPS. Turn on Cloudflare’s Always Use HTTPS to do it instead.

Create the tunnel

The tunnel is locally managed: piqueld configures it, and you never add hostnames to it in Cloudflare.

  1. On any machine, install cloudflared and log in to your Cloudflare account:

    cloudflared tunnel login
    
  2. Create the tunnel:

    cloudflared tunnel create piqueld
    

    It prints the tunnel’s ID, and writes its credentials to ~/.cloudflared/<tunnel-id>.json. Cloudflare’s Create a locally-managed tunnel describes both commands. You don’t need a config.yml or cloudflared tunnel run: piqueld does that part.

  3. Copy the credentials file to your server, readable only by root, such as an agenix or sops-nix secret.

  4. Turn on Always Use HTTPS for the zone in Cloudflare.

Set it up

  1. Point piqueld at the credentials file:

    services.piqueld.settings.ingress = {
      enabled = true;
      tunnel = {
        enabled = true;
        credentials_file = "/run/secrets/cloudflared-tunnel.json";
      };
    };
    

    Or in config.toml, with the file passed by LoadCredential=:

    [ingress.tunnel]
    enabled = true
    credentials_file = "cloudflared-tunnel"
    
  2. Restart the daemon. It replaces the gateway to switch to the tunnel, which briefly interrupts your routes. On NixOS, rebuild the system. Elsewhere, reload the unit you edited first:

    sudo systemctl daemon-reload
    sudo systemctl restart piqueld
    
  3. Point each public hostname at the tunnel with a proxied CNAME record to <tunnel-id>.cfargotunnel.com:

    notes.example.com.   CNAME   6ff42ae2-765d-4adf-8112-31c55c1551ef.cfargotunnel.com.   ; proxied
    

    If piqueld manages your DNS records, it creates them. Otherwise, create them in Cloudflare, or, on the machine where you ran cloudflared tunnel login, with cloudflared tunnel route dns piqueld notes.example.com. piquelctl app route list shows the record each route needs.

Check that the tunnel works

On the host:

$ sudo piquelctl status
...
Public listener: healthy: Caddy is running and routing configuration is applied
Public ingress: Cloudflare Tunnel 6ff42ae2-765d-4adf-8112-31c55c1551ef (connected)
...

In the dashboard, System status shows a Cloudflare Tunnel card, Connected once the tunnel reaches Cloudflare. A public route is ready once piqueld reached it through the tunnel over trusted HTTPS.

Leave the tunnel

To serve public routes on ports 80 and 443 again:

  1. Set tunnel.enabled = false, and rebuild the system or restart the daemon. piqueld replaces the gateway again, stops cloudflared, and deletes its copy of the credentials.
  2. Open TCP ports 80 and 443 in the server’s firewall, as in Enable the gateway.
  3. Replace each proxied CNAME record to the tunnel with A and AAAA records to your server. If piqueld manages your DNS records and you listed your public_addresses, it does that itself.

Troubleshooting

  • dns refresh reports Zone discovery failed with HTTP 403. The token is wrong or expired, or may lack Zone › Zone › Read.
  • dns refresh lists no zones. The token’s zone resources don’t include your zone. Edit the token in Cloudflare, then run dns refresh again.
  • Certificates fail with write record and HTTP 403. The token lacks Zone › DNS › Edit on that zone.
  • A route reports dns_conflict after cloudflared tunnel route dns. With manage_records, piqueld doesn’t own records it didn’t create. Delete the CNAME record, and let piqueld create it.
  • The tunnel stays not connected. The daemon logs include cloudflared’s own logs. While it is disconnected, public routes are unreachable. In the dashboard’s System status, the Cloudflare Tunnel card shows Disconnected, and HTTPS ingress shows Unhealthy.
  • Startup fails with is not a cloudflared tunnel credentials file. The file must be the JSON file cloudflared tunnel create wrote, holding AccountTag, TunnelSecret and TunnelID, not cert.pem or a tunnel token.
  • A deeper name, such as x.staging.example.com, has no valid certificate through the tunnel. Cloudflare’s Universal SSL certificate doesn’t cover it; see the trade-offs. Use Cloudflare’s Advanced Certificate Manager, or keep tunnel hostnames one level below the zone.
  • Switching to or from the tunnel doesn’t happen. If an application’s network is broken, piqueld keeps the gateway in its current mode until the network is repaired or that application’s routes are removed. Until then, the gateway applies no route changes, so deployments of environments with routes fail with ingress_unavailable and are retried. The daemon logs name the application.