NixOS Nginx Redirect www to non www-Domain

by||2 min read
NixOS Nginx Redirects
NixOS Nginx Redirects

To not risk SEO penalties for duplicate content it is often a good advice to redirect from a www to non-www domain. This article shows how we can achieve this with NixOS and nginx.

Let's start with a simple setup. We have a domain, perhaps ersocon.net. We would like the domain to be accessible via SSL and redirect all non SSL requests to its SSL counterpart (http://ersocon.net to https://ersocon.net). Now, having a www.ersocon.net sub-domain as well, we would like to redirect all HTTP requests to its SSL counterpart and then to the non-www version of ersocon.net.

We can start off by a simple set-up in the configuration.nix file, which will already take care of the first part of our requirement:

security.acme.defaults.email = "myemail@ersocon.net";
security.acme.acceptTerms = true;

services.nginx = {
  enable = true;

  virtualHosts."ersocon.net" = {
      enableACME = true;
      forceSSL = true;
      
      locations."/" = {
        proxyPass = "http://localhost:8000";
      };
  };
};

As already mentioned, this config will redirect all HTTP requests (port 80) to SSL requests (port 443).

What we could try now is to add a server alias to this configuration:

serverAliases = ["www.ersocon.net"];

This would enable all requests to www.ersocon.net. But, this is exactly the case we would like to avoid, since it would end up in the duplicate content issue.

For the second attempt we can create a second virtualHost entry for www.ersocon.net:

virtualHosts."www.ersocon.net" = {
  forceSSL = true;
      
  locations."/" = {
    extraConfig = ''
        return 301 https://ersocon.net$request_uri;
    '';
  };
};

With the flag forceSSL it seems like a good idea but unfortunately wont work. In this case, the acme module will run into issues to verify the domain certificate. So, let's remove this flag and add the served ports instead:

virtualHosts."www.ersocon.net" = {
  listen = [ { addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 443; } ];
      
  locations."/" = {
    extraConfig = ''
        return 301 https://ersocon.net$request_uri;
    '';
  };
};

Awesome! The single problem with this, the browser will access www.ersocon.net and run into an invalid certificate issue, since we have no valid Let's Encrypt certificate. But there is a way to verify the certificate for www.ersocon.net as well. We can rewrite our previous config for the acme configuration like this:

# We remove this:
# security.acme.defaults.email = "myemail@ersocon.net";
# security.acme.acceptTerms = true;

# And add this
security.acme = {
  defaults.email = "myemail@ersocon.net";
  acceptTerms = true;

  certs."ersocon.net" = {
    webroot = "/var/lib/acme/challenges-ersocon-net";
    email = "myemail@ersocon.net";
    group = "nginx";
    extraDomainNames = [ "www.ersocon.net" ];
  };
};

With this, we have a certificate valid for ersocon.net and www.ersocon.net. But, to verify the certificate for www.ersocon.net we need to add one more location to our www.ersocon.net virtualHost:

virtualHosts."www.ersocon.net" = {
  listen = [ { addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 443; } ];

  locations."/.well-known/acme-challenge" = {
    root = "/var/lib/acme/challenges-ersocon-net";
    extraConfig = ''
      auth_basic off;
    '';
  };
      
  locations."/" = {
    extraConfig = ''
        return 301 https://ersocon.net$request_uri;
    '';
  };
};

Those changes will complete our setup for Nginx on NixOS. If you would like to see the final result of the nginx.conf configuration, you can request the status of the service to determine the location of the config file:

sudo systemctl status nginx

Thank you for reading this far! Let’s connect. You can @ me on Twitter (@debilofant) with comments, or feel free to follow. Please like/share this article so that it reaches others as well.

© Copyright 2022 - Ersocon - All rights reservedVer. 2.3.6.9