Caddy: Difference between revisions

imported>Malteneuss
m Add code highlighting
mNo edit summary
 
(27 intermediate revisions by 11 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 module:
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 = {
   enable = true;
   enable = true;
   virtualHosts."localhost".extraConfig = ''
   virtualHosts."localhost".extraConfig = ''
     respond "Hello, world!"
     respond "Hello, world!"
   '';
   '';
};
};</syntaxhighlight>
</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 used ports ====
Use <code>curl -iLk localhost</code> to verify the configuration.
 
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.
 
==== Check connection ====
 
You can use <code>curl</code> to test the http(s) connections:
 
<syntaxhighlight lang="bash">
$ curl localhost -i -L -k
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://localhost/
Server: Caddy
date: Sat, 08 Jul 2023 11:56:05 GMT
Content-Length: 0
 
HTTP/2 200
alt-svc: h3=":443"; ma=2592000
content-type: text/plain; charset=utf-8
server: Caddy
content-length: 15
date: Sat, 08 Jul 2023 11:56:05 GMT
 
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.
 
== 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.
For SSL to work, just supply a public domain and ensure HTTP and HTTPS ports are accessible. Caddy will automatically configure TLS:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">services.caddy = {
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 91: Line 39:
   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].
 
* [https://caddyserver.com/docs/quick-starts/reverse-proxy Caddy reverse proxy 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 106: Line 64:
   virtualHosts."example.org" = {
   virtualHosts."example.org" = {
     extraConfig = ''
     extraConfig = ''
       redir https://www.example.org
       redir https://www.example.org{uri} permanent
   '';
   '';
     serverAlias = [ "old.example.org" ];
     serverAliases = [ "old.example.org" ];
};
};
</syntaxhighlight>
</syntaxhighlight>
Line 131: 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 ===


You can use curl to test http(s) calls. However, you must ensure that the "Host" header matches the virtualhost entry of Caddy. For example, when testing locally a config like  
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 ===
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">
<syntaxhighlight lang="nix">
Line 172: 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 181: Line 228:


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