Caddy: Difference between revisions

Added instructions to build Caddy with plugins.
mNo edit summary
 
(8 intermediate revisions by 2 users not shown)
Line 6: Line 6:
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:
 
<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>


* [https://curl.se/docs/manpage.html#--connect-to curl connect-to documentation]
For SSL to work, just supply a public domain and ensure HTTP and HTTPS ports are accessible. Caddy will automatically configure TLS:
* [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 ==
<syntaxhighlight lang="nix">services.caddy = {
 
=== 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 144: Line 88:


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>.
=== 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 ===
=== Passing environment variable secrets/configuring acme_dns ===
Line 162: Line 163:
</syntaxhighlight>
</syntaxhighlight>


=== Adding plug-ins ===
== Troubleshooting ==
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 ===
=== 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 198: 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 238: Line 220:


* [https://caddyserver.com/docs/caddyfile/directives/tls Caddy TLS settings documentation]
* [https://caddyserver.com/docs/caddyfile/directives/tls Caddy TLS settings documentation]
== Adding Plugins ==
Caddy supports being extended with plugins, which must be added during the build process<ref>https://github.com/caddyserver/caddy?tab=readme-ov-file#with-version-information-andor-plugins</ref>. The simplest option to do this in Nix is by using an override to build a customized version of Caddy.
Follow the instructions for [https://github.com/caddyserver/caddy?tab=readme-ov-file#with-version-information-andor-plugins setting up Caddy to build with plugins], either by using xcaddy, or following the manual steps. In your configuration, override Caddy to build using this local Go module. If you created the main.go file in the same location as your caddy Nix config, it can look something like this:
<syntaxhighlight lang="nix">
services.caddy.package = pkgs.caddy.overrideAttrs (
  self: super: {
    src = ./.;
    vendorHash = "";
    subPackages = [ ];
  }
);
</syntaxhighlight>
== See also ==
== See also ==