Caddy: Difference between revisions
imported>Malteneuss mNo edit summary |
|||
(25 intermediate revisions by 9 users not shown) | |||
Line 1: | Line 1: | ||
[https://caddyserver.com/ Caddy] is | [https://caddyserver.com/ Caddy] is an efficient, HTTP/2 capable web server that can serve static and dynamic web pages. | ||
It can also be a reverse proxy to serve multiple web services under one server. Its main features are its simple config setup and automatic HTTPS: It will automatically request and renew a LetsEncrypt certificate so that users of your service get a Browser-trusted and secure connection. | |||
== | == Setup == | ||
To try out Caddy add the following minimal example to your [[NixOS modules | NixOS module]]: | |||
<syntaxhighlight lang="nix> | <syntaxhighlight lang="nix"> | ||
services.caddy = { | { | ||
# ... | |||
services.caddy = { | |||
http://localhost | enable = true; | ||
virtualHosts."localhost".extraConfig = '' | |||
respond "Hello, world!" | |||
''; | |||
}; | |||
} | |||
</syntaxhighlight> | |||
This snippet will let Caddy respond on <code>http://localhost</code> and <code>https://localhost</code> with a dummy text "Hello world!". When no port is mentioned on virtualhost like just <code>localhost</code> instead of <code>localhost:8080</code>, Caddy listens on <code>80</code> and <code>443</code> by default and redirects requests from port 80 (unsecured) to 443 (secured). | |||
==== Check http connection ==== | |||
You can use <code>curl</code> to test the http connections: | |||
<syntaxhighlight lang="bash"> | |||
$ curl localhost -i -L -k | |||
HTTP/1.1 308 Permanent Redirect | |||
Location: https://localhost/ | |||
.. | |||
HTTP/2 200 | |||
alt-svc: h3=":443"; ma=2592000 | |||
content-type: text/plain; charset=utf-8 | |||
... | |||
Hello, world! | |||
</syntaxhighlight> | |||
Here you can see that Caddy automatically redirects from an unsecure http://localhost to a secure https://localhost call. | |||
For local addresses like "localhost" Caddy always generates and uses a self-signed certificate, which curl correctly doesn't trust; use the <code>-k</code> flag to ignore that. | |||
==== Check http(s) connection ==== | |||
When virtualhost and "real" host aren't the same it gets complicated with HTTPS, so the following curl command works: | |||
<syntaxhighlight lang="bash"> | |||
$ curl --connect-to <virtualhost>:443:<realhost>:443 https://<virtualhost> -k | |||
Hello, world! | |||
</syntaxhighlight> | |||
Curl will set <code>Host</code> header and TLS <code>SNI</code> in the request to <code><virtualhost></code> as desired by Caddy, but will make the actual request against the <code><realhost></code>, e.g. a load-balancer or ingress-controller. | |||
Alternatively with http and automatic redirects to https you can extend that call: | |||
<syntaxhighlight lang="bash"> | |||
$ curl --connect-to <virtualhost>:80:<realhost>:80 --connect-to <virtualhost>:443:<realhost>:443 https://<virtualhost> -k -L | |||
Hello, world! | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | * [https://curl.se/docs/manpage.html#--connect-to curl connect-to documentation] | ||
* [https://www.claudiokuenzler.com/blog/693/curious-case-of-curl-ssl-tls-sni-http-host-header Curl on HTTPS, SNI, Host] | |||
* [https://github.com/caddyserver/caddy/issues/2656#issuecomment-1627342466 curl to Caddy over HTTPS] | |||
== Typical configurations == | |||
=== SSL === | === SSL === | ||
Line 29: | Line 69: | ||
Caddy will automatically try to acquire SSL certificates for the specified domain, in this example <code>example.org</code>. This requires you to configure the DNS records of your domain correctly, which should point to the address of your Caddy server. The [[firewall]] ports <code>80</code> and <code>443</code> needs to be opened. | Caddy will automatically try to acquire SSL certificates for the specified domain, in this example <code>example.org</code>. This requires you to configure the DNS records of your domain correctly, which should point to the address of your Caddy server. The [[firewall]] ports <code>80</code> and <code>443</code> needs to be opened. | ||
<syntaxhighlight lang="nix> | <syntaxhighlight lang="nix"> | ||
services.caddy = { | services.caddy = { | ||
enable = true; | enable = true; | ||
Line 42: | Line 82: | ||
} | } | ||
''; | ''; | ||
}; | }; | ||
networking.firewall.allowedTCPPorts = [ 80 443]; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 49: | Line 90: | ||
The following snippet creates a reverse proxy for the domain <code>example.org</code>, redirecting all requests to <code><nowiki>http://10.25.40.6</nowiki></code> | The following snippet creates a reverse proxy for the domain <code>example.org</code>, redirecting all requests to <code><nowiki>http://10.25.40.6</nowiki></code> | ||
<syntaxhighlight lang="nix> | <syntaxhighlight lang="nix"> | ||
services.caddy = { | services.caddy = { | ||
enable = true; | enable = true; | ||
virtualHosts."example.org".extraConfig = '' | virtualHosts."example.org".extraConfig = '' | ||
reverse_proxy http://10.25.40.6 | reverse_proxy http://10.25.40.6 | ||
''; | |||
virtualHosts."another.example.org".extraConfig = '' | |||
reverse_proxy unix//run/gunicorn.sock | |||
''; | |||
}; | |||
</syntaxhighlight>In case you would like to forward the real client IP of the request to the backend, add following headers<syntaxhighlight lang="nix"> | |||
services.caddy = { | |||
virtualHosts."example.org".extraConfig = '' | |||
reverse_proxy http://10.25.40.6 { | |||
header_down X-Real-IP {http.request.remote} | |||
header_down X-Forwarded-For {http.request.remote} | |||
} | |||
''; | ''; | ||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight>Fur further reverse proxy configuration, see [https://caddyserver.com/docs/quick-starts/reverse-proxy upstream documentation]. | ||
=== Redirect === | === Redirect === | ||
Permanent redirect of <code>example.org</code> and <code>old.example.org</code> to <code>www.example.org</code> | |||
<syntaxhighlight lang="nix> | <syntaxhighlight lang="nix"> | ||
services.caddy = { | services.caddy = { | ||
enable = true; | enable = true; | ||
virtualHosts."example.org" = { | virtualHosts."example.org" = { | ||
extraConfig = '' | extraConfig = '' | ||
redir https://www.example.org | redir https://www.example.org{uri} permanent | ||
''; | ''; | ||
serverAliases = [ "old.example.org" ]; | |||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 77: | Line 130: | ||
Serving a PHP application in <code>/var/www</code> on http://localhost . | Serving a PHP application in <code>/var/www</code> on http://localhost . | ||
<syntaxhighlight lang="nix> | <syntaxhighlight lang="nix"> | ||
services.caddy = { | services.caddy = { | ||
enable = true; | enable = true; | ||
Line 91: | Line 144: | ||
You'll need a [[Phpfpm|PHP-FPM]] socket listening on Unix socket path <code>/var/run/phpfpm/localhost.sock</code>. | You'll need a [[Phpfpm|PHP-FPM]] socket listening on Unix socket path <code>/var/run/phpfpm/localhost.sock</code>. | ||
=== Passing environment variable secrets/configuring acme_dns === | |||
To prevent any secrets from being put in the nix store (any NixOS setting that writes a config in the Nix store will expose any secret in it), you can use the following setting<syntaxhighlight lang="nixos"> | |||
services.caddy = { | |||
enable = true; | |||
globalConfig = '' | |||
acme_dns PROVIDER { | |||
api_key {$APIKEY} | |||
api_secret_key {$APISECRETKEY} | |||
} | |||
''; | |||
}; | |||
systemd.services.caddy.serviceConfig.EnvironmentFile = ["/path/to/envfile"]; | |||
</syntaxhighlight>And then at '''/path/to/envfile''':<syntaxhighlight> | |||
APIKEY=YOURKEY | |||
APISECRETKEY=OTHERKEY | |||
</syntaxhighlight> | |||
=== Adding plug-ins === | |||
There are many Issues/PR's about allowing Caddy to be built with plug-ins. Until then, you can use this workaround:<syntaxhighlight lang="nixos"> | |||
services.caddy = { | |||
enable = true; | |||
package = (pkgs.callPackage "${builtins.fetchurl https://raw.githubusercontent.com/jpds/nixpkgs/a33b02fa9d664f31dadc8a874eb1a5dbaa9f4ecf/pkgs/servers/caddy/default.nix}" { | |||
externalPlugins = [ | |||
{ name = "caddy-dns/porkbun"; repo = "github.com/caddy-dns/porkbun"; version = "4267f6797bf6543d7b20cdc8578a31764face4cf"; } | |||
# Set version to target repository commit hash | |||
]; | |||
vendorHash = ""; # Add this as explained in https://github.com/NixOS/nixpkgs/pull/259275#issuecomment-1763478985 | |||
}); | |||
globalConfig = '' | |||
... | |||
''; | |||
} | |||
</syntaxhighlight> | |||
== Debugging == | |||
=== Check used ports === | |||
To check if Caddy is running and listening as configured you can run <code>netstat</code>: | |||
<syntaxhighlight lang="bash"> | |||
$ netstat -tulpn | |||
Active Internet connections (only servers) | |||
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name | |||
tcp 0 0 127.0.0.1:2019 0.0.0.0:* LISTEN 1202/caddy | |||
tcp6 0 0 :::80 :::* LISTEN 1202/caddy | |||
tcp6 0 0 :::443 :::* LISTEN 1202/caddy | |||
udp6 0 0 :::443 :::* 1202/caddy | |||
</syntaxhighlight> | |||
The tcp (ipv4) socket port 2019 is Caddy's management endpoint, for when you want manage its config via web REST calls instead of Nix (ignore). | |||
The tcp6 (an ipv6 socket that also listens on ipv4) socket on port 80 (HTTP) and 443 (HTTPS) indicate that our virtualhost config was used. | |||
=== Virtualhost and real host not identical === | |||
When you connect to Caddy must ensure that the "Host" header matches the virtualhost entry of Caddy. For example, when testing locally a config like | |||
<syntaxhighlight lang="nix"> | |||
services.caddy = { | |||
enable = true; | |||
virtualHosts."example.org".extraConfig = '' | |||
respond "Hello, world!" | |||
''; | |||
}; | |||
</syntaxhighlight> | |||
you must send the request against "localhost" and manually override the host header to "example.org": | |||
<syntaxhighlight lang="bash"> | |||
$ curl localhost -i -H "Host: example.org" | |||
HTTP/1.1 308 Permanent Redirect | |||
Connection: close | |||
Location: https://example.org/ | |||
Server: Caddy | |||
... | |||
</syntaxhighlight> | |||
Above you also see the redirect from http://localhost to https://example.org; Caddy always redirects from the unsecure to the secure port of your virtualhost. | |||
If the response is empty, try setting a port number like 80 and/or try a local TLS security certificate instead of global LetsEncrypt: | |||
<syntaxhighlight lang="nix"> | |||
services.caddy = { | |||
enable = true; | |||
virtualHosts."example.org:80".extraConfig = '' | |||
respond "Hello, world!" | |||
tls internal | |||
''; | |||
}; | |||
</syntaxhighlight> | |||
With "tls internal" Caddy will generate a local certificate, which is good when testing locally and/or you don't have internet access (e.g. inside a nixos-container). | |||
* [https://caddyserver.com/docs/caddyfile/directives/tls Caddy TLS settings documentation] | |||
== See also == | == See also == | ||
Line 96: | Line 243: | ||
* [https://search.nixos.org/options?query=services.caddy Available NixOS service options] | * [https://search.nixos.org/options?query=services.caddy Available NixOS service options] | ||
* [https://caddyserver.com/docs/ Official Caddy documentation] | * [https://caddyserver.com/docs/ Official Caddy documentation] | ||
* [https://github.com/NixOS/nixpkgs/blob/nixos-23.05/nixos/modules/services/web-servers/caddy/default.nix NixOS service | * [https://github.com/NixOS/nixpkgs/blob/nixos-23.05/nixos/modules/services/web-servers/caddy/default.nix NixOS service definition] | ||
[[Category:Applications]] | [[Category:Applications]] | ||
[[Category: | [[Category:Server]] | ||
[[Category:Networking]] |