Nixos Nginx ACME ssl certificates for multiple domains

Hi,

I have two domains for my webserver a company.org and company.com .
I configured nginx and ACME the following way:

# Web Server ssl and non ssl
  security.acme.acceptTerms = true;
  security.acme.email = "web+acme@company.org";
  # /var/lib/acme/.challenges must be writable by the ACME user
# and readable by the Nginx user. The easiest way to achieve
# this is to add the Nginx user to the ACME group.
users.users.nginx.extraGroups = [ "acme" ];
  environment.systemPackages = with pkgs; [ tcpdump openssl ];
  services.nginx = {
    enable = true;
    virtualHosts."company.org" = {
        root = "/www/webroot/website";
        listen = [
          { addr = "0.0.0.0"; port = 80; }
          { addr = "0.0.0.0"; port = 443; ssl = true;}
        ];
        addSSL = true;
        enableACME = true;
    };


  virtualHosts."company.com" = {
        root = "/www/webroot/website";
        listen = [
          { addr = "0.0.0.0"; port = 80; }
          { addr = "0.0.0.0"; port = 443; ssl = true;}
        ];
        addSSL = true;
        enableACME = true;
    };
  };

}

I successfully got the ssl certificate for company.org but when i try to connect to company.com . I get an Certificate Error because the common name is invalid (it uses the certificate of company.org)

Does anyone know how i should configure nginx/acme to be able to work with multiple domains ?

1 Like

You want to manually request a certificate per host and then assign them with services.nginx.virtualHosts..useACMEHost

2 Likes

Here you may find one example. I post this here as I needed several days and frustrating trials before I got this working. If someone else is trying out the same thing, I hope that this will save some time.

In the example below you may find:

  1. Security.acme section to manually tell nix to make the certs for two domains
  2. Separate virtual host sections for https for both domains, as well as separate sections for http for the http cert renewals together with redirections to https for everything else
  3. Note that the both domains point to the same directories (they could be separate)
  4. The dns of both domains point to the same ip address (configured elsewhere).

I also added openssl to systemPackages, but I’m not sure if that is required (I didn’t want to try anymore - when trying out these with a single domains, I got a feeling that it is required).

  security.acme = {
    acceptTerms = true;
    defaults.email = "cert+admin@example.com";
    certs."example.com" = {
      webroot = "/var/lib/acme/challenges-com";
      email = "cert+admin@example.com";
      group = "nginx";
      extraDomainNames = [ "www.example.com" ];
    };
    certs."example.de" = {
      webroot = "/var/lib/acme/challenges-de";
      email = "cert+admin@example.de";
      group = "nginx";
      extraDomainNames = [ "www.example.de" ];
    };
  };
  users.users.nginx.extraGroups = [ "acme" ];
  services.nginx = {
    enable = true;
    logError = "stderr info";
    virtualHosts = {
      "example.de" = {
        # forceSSL = true;
        addSSL = true;
        useACMEHost = "example.de";
        # All serverAliases will be added as extra domain names on the certificate.
        serverAliases = [ "*.example.de" ];
        acmeRoot = "/var/lib/acme/challenges-de";
        locations."/" = { root = "/var/www/de"; };
        # listen = [ { addr = "0.0.0.0"; port = 443; ssl = true;} { addr = "[::0]"; port = 443; ssl = true;} ];
        #sslCertificate = "/var/lib/acme/example.de/fullchain.pem";
        #sslCertificateKey = "/var/lib/acme/example.de/key.pem";
        #sslTrustedCertificate = "/var/lib/acme/example.de/chain.pem";
      };
      "example.de80" = {
        serverName = "example.de";
        serverAliases = [ "*.example.de" ];
        locations."/.well-known/acme-challenge" = {
          root = "/var/lib/acme/challenges-de";
          extraConfig = ''
            auth_basic off;
          '';
        };
        locations."/" = { return = "301 https://$host$request_uri"; };
        listen = [ { addr = "0.0.0.0"; port = 80; } { addr = "[::0]"; port = 80; } ];
      };
      "example.com" = {
        addSSL = true;
        useACMEHost = "example.com";
        serverAliases = [ "*.example.com" ];
        acmeRoot = "/var/lib/acme/challenges-com";
        locations."/" = { root = "/var/www/de"; };
      };
      "example.com80" = {
        serverName = "example.com";
        serverAliases = [ "*.example.com" ];
        locations."/.well-known/acme-challenge" = {
          root = "/var/lib/acme/challenges-com";
          extraConfig = ''
            auth_basic off;
          '';
        };
        locations."/" = { return = "301 https://$host$request_uri"; };
        listen = [ { addr = "0.0.0.0"; port = 80; } { addr = "[::0]"; port = 80; } ];
      };
    };
  };

Some random findings:

  • It was very easy to build a configuration that didn’t work
  • E.g. without that addSSL = true; but with extra location and those commented Certificate locations, most of the time the corresponding certificate lines were not added into the nginx.conf
  • Also, it was quite easy to make a nginx.conf that had multiple times the same location so that nginx refused to start.
  • I didn’t try to find minimal working configuration. Some of the settings above might not needed.

Anyhow, this leads to a thought that maybe the documentation could have couple of examples of typical nginx configurations, a kind of “how to do this and that” section.

Another thought: it would be cool if there were a possibility to utilize the staging certificate service when trying to find a new configuration settings. Or is it possible already? (I run into that 1 hour limit quite many times and that staging would help with that.)

1 Like

I know it’s not the main topic, but when I see stuff with NGinx and HTTPS, it always makes me think to this… and usually people thank me later !

Have you considered using Caddy instead of NGinx ? The HTTPS is enabled and configured by default.

1 Like

no, it isn’t.

security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";

That is not required. You only need to set forceSSL and useACMEHost for the vhost and configure security.acme then you are done.


This is not hard at all if you have a basic understanding of nginx.
Caddy won’t magically solve all your problems especially if you are not very familiar what you need to do to configure a web server.

1 Like