Nginx + PAM + access to /etc/shadow?

Hi, I’m trying to setup a nixos-container with nginx + PAM authentication. I have succeeded with a dirty hack, and I’m wondering if there is a better way.

By default nginx runs as the nginx user.
By default, /etc/shadow is 0600 and owned by root:root

Despite setting
security.pam.services.nginx = {};

and setting
''auth_pam_service_name "nginx";'' in the extraConfig of nginx,
nginx will always return Authentication failed when trying to login.

Then, I’ve installed ‘pamtester’ to find out what is going on.

It seems that when not using root, it is impossible to access the /etc/shadow file. But I’m confused, because I thought the unix_chkpwd utility would take care of this.

Anyway, there are multiple ugly solutions to this problem.

  1. run nginx as root
  2. chmod 0640 /etc/shadow
    and create a group ‘shadow’ and chgrp shadow /etc/shadow and add nginx user to group shadow
    This seems impossible with Nix, because the group and mode of /etc/shadow seems hardcoded by some Perl script.

why can’t nginx access /etc/shadow despite the presence of unix_chkpwd utility
Is there a better way than running nginx as root?

Thanks in advance!

I have updated the WIKI, Nginx - NixOS Wiki
Please check if the information is correct. I still think giving root access to nginx is probably not the correct solution, but I’m not an expert on PAM

Hi, I came across this post after hitting the same issue. I believe the reason why unix_chkpwd can’t access /etc/shadow is described here:
https://www.reddit.com/r/NixOS/comments/76irgt/pam_auth_without_revealing_etcshadows/dofwe2g/

If you look at the source for unix_chkpwd (https://github.com/linux-pam/linux-pam/blob/48f44125fac8873237ade9e94942f82a8e6d6e1d/modules/pam_unix/unix_chkpwd.c#L129-L146) you see a check whether it’s being executed as root. In the case of nginx unix_chkpwd is called by the nginx user in which case it forbids authentication as another user (thus pam auth with nginx would only work for the nginx user). I think this is generally reasonable/desirable behavior for 99% of use cases (ex. screen locking, etc.). IMO running nginx as root is unacceptable security-wise, and chgrp-ing /etc/shadow is the “correct” way to go. This is consistent with the suggestion here GitHub - sto/ngx_http_auth_pam_module: Nginx module to use PAM for simple http authentication :

if you want to use the pam_unix.so module to autenticate users you need to let the web server user to read the /etc/shadow file if that does not scare you (on Debian like systems you can add the www-data user to the shadow group).

1 Like

I agree, however, at this point I don’t know how to chgrp /etc/shadow since it group and mode are hard set by a perl script

I think the only way to fix this is to change update-users-group.pl, so I’ve created an issue: /etc/shadow should belong to the shadow group · Issue #93580 · NixOS/nixpkgs · GitHub

1 Like

Unfortunately the script still creates (hardcodes) the permission of /etc/shadow to 0600
so despite adding nginx to the shadow group, it cannot access /etc/shadow, so the problem and workaround in the OP remains in 20.09

On top of that, it seems unix_chkpwd has issues:


Nov 01 22:31:55 server [2748]: pam_unix(nginx:auth): unix_chkpwd abnormal exit: 134
Nov 01 22:31:55 server [2748]: pam_unix(nginx:auth): authentication failure; logname= uid=60 euid=60 tty= ruser= rhost=12.123.123.12  user=kvtb
Nov 01 22:31:55 server systemd-coredump[2752]: [🡕] Process 2750 (unix_chkpwd) of user 60 dumped core.
Nov 01 22:31:56 server nginx[2748]: 2020/11/01 22:31:55 [error] 2748#2748: *1 PAM: user 'kvtb' - not authenticated:
 Authentication failure, client: 12.123.123.12, server: mydomain.com, request: "GET /privatearea/ HTTP/2.0", host: "mydomain.com"

The permission issue of /etc/passwd was recently fixed: nixos/update-users-groups: read access to /etc/shadow for group shadow by jluttine · Pull Request #116644 · NixOS/nixpkgs · GitHub

However, I’m also getting the same issue with unix_chkpwd: PAM authentication for nginx not working on NixOS · Issue #117578 · NixOS/nixpkgs · GitHub

Any ideas what it’s about and/or how to debug?

is anyone who has responded here running pre-21.05, does it solve the problem described in the first post?

While trying to upgrade my server to NixOS 21.05, I noticed nginx + PAM is quite broken.

nginx will core dump as soon as a connection is made.

Are there more people using nginx+PAM who can verify this combination is indeed broken on 21.05?
perhaps @jluttine ?

Just a hunch but were there hardening options added to nginx which breaks this functionality?

It could be that systemd has something to do with this.

I’ve spent a few hours today to see if I can find the cause and solve the problem.

When I run nginx directly from the command line, it does not crash when accessing a location with auth_pam
But the exact same configuration but started with systemctl will crash the nginx work processes.

I don’t know enough about systemd internals to debug this any further

@adv_nomad also ran into some issues with our systemd service for nginx and there was some discussion about that in Nginx worker processes exit with signal 31 when running via systemd.

Maybe you could try to adapt some of the work in that thread to your config and see if you have some success. If you can’t make any progress with that I can try to find some time to look at it.

It looks like the following works. It could be that even less options are required, so this is the maximum number of option overrides to make it work for PAM authentication:


  security.pam.services.nginx.setEnvironment = false;
  systemd.services.nginx.serviceConfig = {
    SupplementaryGroups = [ "shadow" ];
    NoNewPrivileges = lib.mkForce false;
    PrivateDevices = lib.mkForce false;
    ProtectHostname = lib.mkForce false;
    ProtectKernelTunables = lib.mkForce false;
    ProtectKernelModules = lib.mkForce false;
    RestrictAddressFamilies = lib.mkForce [ ];
    LockPersonality = lib.mkForce false;
    MemoryDenyWriteExecute = lib.mkForce false;
    RestrictRealtime = lib.mkForce false;
    RestrictSUIDSGID = lib.mkForce false;
    SystemCallArchitectures = lib.mkForce "";
    ProtectClock = lib.mkForce false;
    ProtectKernelLogs = lib.mkForce false;
    RestrictNamespaces = lib.mkForce false;
    SystemCallFilter = lib.mkForce "";
  };

  services.nginx = {
    enable = true;

    additionalModules = [ pkgs.nginxModules.pam ];

    virtualHosts."mydomain.com" = {
      root = "/srv/public_html";

      locations."/protected/" = {
        extraConfig = ''
          auth_pam  "PAM password Required";
          auth_pam_service_name "nginx";
        '';
      };

    };
  };

I’m glad you figured it out. If you’re able it would be great if you could figure out exactly what is needed and file an issue or PR.