Jump to content

Caddy: Difference between revisions

From NixOS Wiki
imported>Onny
mNo edit summary
Onny (talk | contribs)
No edit summary
 
(36 intermediate revisions by 11 users not shown)
Line 1: Line 1:
[https://caddyserver.com/ Caddy] is a HTTP/2 capable web server with automatic HTTPS.
[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.


== Installation ==
== Setup ==


The example snippet below will run Caddy on http://localhost and serving an [http://localhost/example.html example.html] page.
To try out Caddy add the following minimal example to your [[NixOS modules | NixOS module]]:


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
<syntaxhighlight lang="nix">services.caddy = {
services.caddy = {
   enable = true;
   enable = true;
   extraConfig = ''
   virtualHosts."localhost".extraConfig = ''
     http://localhost {
     respond "Hello, world!"
      encode gzip
      file_server
      root * ${
        pkgs.runCommand "testdir" {} ''
          mkdir "$out"
          echo hello world > "$out/example.html"
        ''
      }
    }
   '';
   '';
};
};</syntaxhighlight>
</nowiki>}}
== Configuration examples ==


=== SSL ===
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).


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:


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
<syntaxhighlight lang="nix">services.caddy = {
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"
      ''
    }
   '';
   '';
};
};  
</nowiki>}}
 
networking.firewall.allowedTCPPorts = [ 80 443 ];</syntaxhighlight>
 
== Configuration ==
 
=== Plug-ins ===
{{Note|Parts of this module are not yet stable will be available with the upcoming NixOS release 25.05.}}
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>
 
=== Reverse proxy ===
=== Reverse proxy ===


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>


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
<syntaxhighlight lang="nix">
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 = ''
</nowiki>}}
    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>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">
services.caddy = {
  enable = true;
  virtualHosts."example.org" = {
    extraConfig = ''
      redir https://www.example.org{uri} permanent
  '';
    serverAliases = [ "old.example.org" ];
};
</syntaxhighlight>
 
=== PHP FastCGI ===
 
Serving a PHP application in <code>/var/www</code> on http://localhost .
 
<syntaxhighlight lang="nix">
services.caddy = {
  enable = true;
  virtualHosts."http://localhost" = {
    extraConfig = ''
      root    * /var/www
      file_server
      php_fastcgi unix/var/run/phpfpm/localhost.sock
    '';
  };
};
</syntaxhighlight>
 
You'll need a [[Phpfpm|PHP-FPM]] socket listening on Unix socket path <code>/var/run/phpfpm/localhost.sock</code>.


<syntaxhighlight lang="nix>
=== Passing environment variable secrets/configuring acme_dns ===
caddy.virtualHosts."example.org" = {
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">
   extraConfig = ''
services.caddy = {
     redir https://www.example.org
   enable = true;
  globalConfig = ''  
     acme_dns PROVIDER {
      api_key {$APIKEY}
      api_secret_key {$APISECRETKEY}
    }
   '';
   '';
  serverAlias = [ "old.example.org" ];
};
};
systemd.services.caddy.serviceConfig.EnvironmentFile = ["/path/to/envfile"];
</syntaxhighlight>And then at '''/path/to/envfile''':<syntaxhighlight>
APIKEY=YOURKEY
APISECRETKEY=OTHERKEY
</syntaxhighlight>
</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">
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 ==
* [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 definition]


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

Latest revision as of 12:18, 8 April 2025

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 module:

services.caddy = {
  enable = true;
  virtualHosts."localhost".extraConfig = ''
    respond "Hello, world!"
  '';
};

This snippet will let Caddy respond on http://localhost and https://localhost with a dummy text "Hello world!". When no port is mentioned on virtualhost like just localhost instead of localhost:8080, Caddy listens on 80 and 443 by default and redirects requests from port 80 (unsecured) to 443 (secured).

For SSL to work, just supply a public domain and ensure HTTP and HTTPS ports are accessible. Caddy will automatically configure TLS:

services.caddy = {
  enable = true;
  virtualHosts."example.org".extraConfig = ''
    respond "Hello, world!"
  '';
}; 

networking.firewall.allowedTCPPorts = [ 80 443 ];

Configuration

Plug-ins

Note: Parts of this module are not yet stable will be available with the upcoming NixOS release 25.05.

Following example is adding the plugin powerdns in version 1.0.1 to your Caddy binary

services.caddy = {
  enable = true;
  package = pkgs.caddy.withPlugins {
    plugins = [ "github.com/caddy-dns/powerdns@v1.0.1" ];
    hash = "sha256-F/jqR4iEsklJFycTjSaW8B/V3iTGqqGOzwYBUXxRKrc=";
  };
};

Reverse proxy

The following snippet creates a reverse proxy for the domain example.org, redirecting all requests to http://10.25.40.6

services.caddy = {
  enable = true;
  virtualHosts."example.org".extraConfig = ''
    reverse_proxy http://10.25.40.6
  '';
  virtualHosts."another.example.org".extraConfig = ''
    reverse_proxy unix//run/gunicorn.sock
  '';
};

In case you would like to forward the real client IP of the request to the backend, add following headers

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}
    }
  '';
};

Fur further reverse proxy configuration, see upstream documentation.

Redirect

Permanent redirect of example.org and old.example.org to www.example.org

services.caddy = {
  enable = true;
  virtualHosts."example.org" = {
    extraConfig = ''
      redir https://www.example.org{uri} permanent
   '';
    serverAliases = [ "old.example.org" ];
};

PHP FastCGI

Serving a PHP application in /var/www on http://localhost .

services.caddy = {
  enable = true;
  virtualHosts."http://localhost" = {
    extraConfig = ''
      root    * /var/www
      file_server
      php_fastcgi unix/var/run/phpfpm/localhost.sock
    '';
  };
};

You'll need a PHP-FPM socket listening on Unix socket path /var/run/phpfpm/localhost.sock.

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

services.caddy = {
  enable = true;
  globalConfig = ''    
    acme_dns PROVIDER {
      api_key {$APIKEY}
      api_secret_key {$APISECRETKEY}
    }
  '';
};
systemd.services.caddy.serviceConfig.EnvironmentFile = ["/path/to/envfile"];

And then at /path/to/envfile:

APIKEY=YOURKEY
APISECRETKEY=OTHERKEY

Troubleshooting

Check used ports

To check if Caddy is running and listening as configured you can run netstat:

$ 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

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

services.caddy = {
  enable = true;
  virtualHosts."example.org".extraConfig = ''
    respond "Hello, world!"
  '';
};

you must send the request against "localhost" and manually override the host header to "example.org":

$ curl localhost -i -H "Host: example.org"
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://example.org/
Server: Caddy
...

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:

services.caddy = {
  enable = true;
  virtualHosts."example.org:80".extraConfig = ''
    respond "Hello, world!"
    tls internal
  '';
};

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).

See also