diff --git a/docs/ref/integration/reverse-proxy.md b/docs/ref/integration/reverse-proxy.md index 2729786a5..7cc289bb1 100644 --- a/docs/ref/integration/reverse-proxy.md +++ b/docs/ref/integration/reverse-proxy.md @@ -38,8 +38,9 @@ values to your Headscale [configuration file](../configuration.md): ```yaml title="config.yaml" hl_lines="1" server_url: https:// -tls_cert_path: "" -tls_key_path: "" +tls: + cert_path: "" + key_path: "" ``` Headscale logs `WRN listening without TLS but ServerURL does not start with http://` during startup. This is expected @@ -92,8 +93,9 @@ metrics_listen_addr: 127.0.0.1:9090 trusted_proxies: - 127.0.0.1/32 - ::1/128 -tls_cert_path: "" -tls_key_path: "" +tls: + cert_path: "" + key_path: "" ``` ### Apache diff --git a/docs/ref/tls.md b/docs/ref/tls.md index 3ea9870fc..7643476e2 100644 --- a/docs/ref/tls.md +++ b/docs/ref/tls.md @@ -2,29 +2,32 @@ ## Bring your own certificate -Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_key_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. +Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls.cert_path` and `tls.key_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. ```yaml title="config.yaml" -tls_cert_path: "" -tls_key_path: "" +tls: + cert_path: "" + key_path: "" ``` The certificate should contain the full chain, else some clients, like the Tailscale Android client, will reject it. ## Let's Encrypt / ACME -To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. +To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls.acme.hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls.acme.cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. ```yaml title="config.yaml" -tls_letsencrypt_hostname: "" -tls_letsencrypt_listen: ":http" -tls_letsencrypt_cache_dir: ".cache" -tls_letsencrypt_challenge_type: HTTP-01 +tls: + acme: + hostname: "" + http_listen: ":http" + cache_dir: /var/lib/headscale/cache + challenge_type: HTTP-01 ``` ### Challenge types -Headscale only supports two values for `tls_letsencrypt_challenge_type`: `HTTP-01` (default) and `TLS-ALPN-01`. +Headscale supports three values for `tls.acme.challenge_type`: `HTTP-01` (default), `TLS-ALPN-01` and `DNS-01`. `DNS-01` publishes the challenge through the DNS provider in `dns.challenge.provider`. #### HTTP-01 @@ -32,9 +35,9 @@ For `HTTP-01`, headscale must be reachable on port 80 for the Let's Encrypt auto !!! warning "`listen_addr` cannot also be port 80" - `listen_addr` and `tls_letsencrypt_listen` must not bind the same TCP socket. A common mistake is setting `listen_addr: 0.0.0.0:80` together with `tls_letsencrypt_hostname` — both endpoints try to claim port 80, and the second bind fails with `address already in use` even though no other process is involved. Headscale validates this at startup and refuses to launch with a `Fatal config error: listen_addr and tls_letsencrypt_listen would bind the same TCP socket` message. Use `listen_addr: 0.0.0.0:443` (or any non-80 port) when HTTP-01 is enabled. + `listen_addr` and `tls.acme.http_listen` must not bind the same TCP socket. A common mistake is setting `listen_addr: 0.0.0.0:80` together with `tls.acme.hostname` — both endpoints try to claim port 80, and the second bind fails with `address already in use` even though no other process is involved. Headscale validates this at startup and refuses to launch with a `Fatal config error: listen_addr and tls.acme.http_listen would bind the same TCP socket` message. Use `listen_addr: 0.0.0.0:443` (or any non-80 port) when HTTP-01 is enabled. -If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. +If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls.acme.http_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls.acme.http_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls.acme.http_listen`. #### TLS-ALPN-01 @@ -42,12 +45,12 @@ For `TLS-ALPN-01`, headscale listens on the ip:port combination defined in `list ### Technical description -Headscale uses [autocert](https://pkg.go.dev/golang.org/x/crypto/acme/autocert), a Golang library providing [ACME protocol](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment) verification, to facilitate certificate renewals via [Let's Encrypt](https://letsencrypt.org/about/). Certificates will be renewed automatically, and the following can be expected: +Headscale uses [CertMagic](https://github.com/caddyserver/certmagic), a Golang library providing [ACME protocol](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment) verification, to facilitate certificate renewals via [Let's Encrypt](https://letsencrypt.org/about/). Certificates will be renewed automatically, and the following can be expected: - Certificates provided from Let's Encrypt have a validity of 3 months from date issued. -- Renewals are only attempted by headscale when 30 days or less remains until certificate expiry. -- Renewal attempts by autocert are triggered at a random interval of 30-60 minutes. -- No log output is generated when renewals are skipped, or successful. +- Renewals start when a third of the validity remains. +- Renewal checks run every 10 minutes. +- Log lines from the library are logged with `logger=acme`. #### Checking certificate expiry @@ -62,21 +65,3 @@ $ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 notBefore=Feb 8 09:48:26 2024 GMT notAfter=May 8 09:48:25 2024 GMT ``` - -#### Log output from the autocert library - -As these log lines are from the autocert library, they are not strictly generated by headscale itself. - -```plaintext -acme/autocert: missing server name -``` - -Likely caused by an incoming connection that does not specify a hostname, for example a `curl` request directly against the IP of the server, or an unexpected hostname. - -```plaintext -acme/autocert: host "[foo]" not configured in HostWhitelist -``` - -Similarly to the above, this likely indicates an invalid incoming request for an incorrect hostname, commonly just the IP itself. - -The source code for autocert can be found [here](https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.19.0:acme/autocert/autocert.go)