Caddy: Difference between revisions

fix links
mNo edit summary
 
(19 intermediate revisions by 6 users not shown)
Line 2: Line 2:
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.
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.


== Get started ==
== Setup ==


To try out Caddy add the following minimal example to your [[NixOS modules | NixOS module]]:
To try out Caddy add the following minimal example to your [[NixOS modules | NixOS module]]:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">services.caddy = {
{
  enable = true;
  # ...
  virtualHosts."localhost".extraConfig = ''
  services.caddy = {
    respond "Hello, world!"
    enable = true;
  '';
    virtualHosts."localhost".extraConfig = ''
};</syntaxhighlight>
      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).
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 ====
Use <code>curl -iLk localhost</code> to verify the configuration.


You can use <code>curl</code> to test the http connections:
For SSL to work, just supply a public domain and ensure HTTP and HTTPS ports are accessible. Caddy will automatically configure TLS:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="nix">services.caddy = {
$ 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>
 
* [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 ===
 
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">
services.caddy = {
   enable = true;
   enable = true;
   virtualHosts."example.org".extraConfig = ''
   virtualHosts."example.org".extraConfig = ''
     encode gzip
     respond "Hello, world!"
    file_server
    root * ${
      pkgs.runCommand "testdir" {} ''
        mkdir "$out"
        echo hello world > "$out/example.html"
      ''
    }
   '';
   '';
};  
};  
networking.firewall.allowedTCPPorts = [ 80 443];  
 
</syntaxhighlight>
networking.firewall.allowedTCPPorts = [ 80 443 ];</syntaxhighlight>
 
== Configuration ==


=== Reverse proxy ===
=== Reverse proxy ===
Line 100: Line 44:
   '';
   '';
};
};
</syntaxhighlight>
</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 = {
* [https://caddyserver.com/docs/quick-starts/reverse-proxy Caddy reverse proxy documentation]
  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>Fur further reverse proxy configuration, see [https://caddyserver.com/docs/quick-starts/reverse-proxy upstream documentation].


=== Redirect ===
=== Redirect ===


Redirecting <code>example.org</code> and <code>old.example.org</code> to <code>www.example.org</code>
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">
Line 113: Line 64:
   virtualHosts."example.org" = {
   virtualHosts."example.org" = {
     extraConfig = ''
     extraConfig = ''
       redir https://www.example.org{uri}
       redir https://www.example.org{uri} permanent
   '';
   '';
     serverAlias = [ "old.example.org" ];
     serverAliases = [ "old.example.org" ];
};
};
</syntaxhighlight>
</syntaxhighlight>
Line 138: Line 89:
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>.


== Debugging ==
=== Plug-ins ===
 
Following example is adding the plugin powerdns in version 1.0.1 to your Caddy binary
 
<syntaxhighlight lang="nix">
services.caddy = {
  enable = true;
  package = pkgs.caddy.withPlugins {
    plugins = [ "github.com/caddy-dns/powerdns@v1.0.1" ];
    hash = "sha256-F/jqR4iEsklJFycTjSaW8B/V3iTGqqGOzwYBUXxRKrc=";
  };
};
</syntaxhighlight>
 
Get the correct hash by leaving the string empty at first and after rebuild, insert the hash which the build process calculated.
 
 
In case a plugin has no version tag, you'll have to query it first. In this example we'll do this for the plugin caddy-webdav
 
<syntaxhighlight lang="sh">
$ go mod init temp
$ go get github.com/mholt/caddy-webdav
$ grep 'caddy-webdav' go.mod
        github.com/mholt/caddy-webdav v0.0.0-20241008162340-42168ba04c9d // indirect
</syntaxhighlight>
 
Add this version string to your final config
 
<syntaxhighlight lang="nix">
services.caddy = {
  enable = true;
  package = pkgs.caddy.withPlugins {
    plugins = [ "github.com/caddy-dns/caddy-webdav@v0.0.0-20241008162340-42168ba04c9d" ];
    hash = "sha256-F/jqR4iEsklJFycTjSaW8B/V3iTGqqGOzwYBUXxRKrc=";
  };
};
</syntaxhighlight>
 
=== uWSGI apps ===
Serving uWSGI apps with Caddy also requires a plugin, in this example we'll use [https://github.com/wxh06/caddy-uwsgi-transport caddy-uwsgi-transport]. See section above on how to fetch and update plugins.<syntaxhighlight lang="nix">
services.caddy = {
  package = pkgs.caddy.withPlugins {
    plugins = [ "github.com/BadAimWeeb/caddy-uwsgi-transport@v0.0.0-20240317192154-74a1008b9763" ];
    hash = "sha256-aEdletYtVFnQMlWL6YW4gUgrrTBatoCIuugA/yvMGmI=";
  };
  virtualHosts = {
    "myapp.example.org" = {
      extraConfig = ''
        reverse_proxy unix/${config.services.uwsgi.runDir}/myapp.sock {
          transport uwsgi
        }
      '';
  };
};
 
</syntaxhighlight>This example will serve a [[uWSGI]] app, provided by a unix socket file, on the host <code>myapp.example.org</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>
 
== Troubleshooting ==


=== Check used ports ===
=== Check used ports ===
To check if Caddy is running and listening as configured you can run <code>netstat</code>:
To check if Caddy is running and listening as configured you can run <code>netstat</code>:


Line 157: Line 181:


=== Virtualhost and real host not identical ===
=== 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  
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  


Line 197: Line 220:


* [https://caddyserver.com/docs/caddyfile/directives/tls Caddy TLS settings documentation]
* [https://caddyserver.com/docs/caddyfile/directives/tls Caddy TLS settings documentation]
== See also ==
== See also ==


Line 206: Line 228:


[[Category:Applications]]
[[Category:Applications]]
[[Category:Web Servers]]
[[Category:Server]]
[[Category:Networking]]