Unable to configure nginx with php-fpm

I have a problem with my php-fpm + nginx config:

  services.phpfpm.pools.pool = {
    user = "nobody";
    settings = {
      pm = "dynamic";
      "listen.owner" = config.services.nginx.user;
      "pm.max_children" = 5;
      "pm.start_servers" = 2;
      "pm.min_spare_servers" = 1;
      "pm.max_spare_servers" = 3;
      "pm.max_requests" = 500;
    };
  };

  services.nginx = {
    enable = true;
    recommendedOptimisation = true;
    recommendedProxySettings = true;
    clientMaxBodySize = "100M";
    package = pkgs.nginxQuic;

    virtualHosts."domain.it" = {
      root = "/var/www/domain.it";
      forceSSL = true;
      sslCertificate = "/var/lib/acme/domain.it/cert.pem";
      sslCertificateKey = "/var/lib/acme/domain.it/key.pem";
      http3 = true;

      locations."/".extraConfig = ''
        location ~* \.php(/|$) {
          fastcgi_split_path_info ^(.+\.php)(/.*)$;
          fastcgi_pass  unix:${config.services.phpfpm.pools.pool.socket};

          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_param PATH_INFO       $fastcgi_path_info;

          include ${config.services.nginx.package}/conf/fastcgi_params;
          include ${pkgs.nginx}/conf/fastcgi.conf;
        }
      '';
    };

When I try to access a simple php script in browser I only get File not found..

Mar 14 01:01:59 nixos-nginx nginx[99994]: 2023/03/14 01:01:59 [error] 99994#99994: *5 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 2001:9e8:30da:fc00:764b:6989:7b11:527d, server: domain.it, request: "GET /update.php HTTP/2.0", upstream: "fastcgi://unix:/run/phpfpm/pool.sock:", host: "domain.it"
1 Like

I would recommend to copy an existing config like the nextcloud one.

I’ve got the same issues with my very similar configuration.
I copied the example from this NixOS Wiki Page: Phpfpm - NixOS Wiki

I used the exact example and have no other nginx or php configuration in my NixOS.
Got the following errors in the journalctl

Aug 15 22:16:29 XXXXX nginx[28966]: 2023/08/15 22:16:29 [error] 28966#28966: *13 FastCGI sent in stderr: “Primary script unknown” while reading response header from upstream, client: XXX.XXX.XXX.XXX, server: XXXXX.com, request: “GET / HTTP/1.1”, upstream: “fastcgi://unix:/run/phpfpm/phpdemo.sock:”, host: “XXX.XXX.XXX.XXX”

Seems that the NixOS Wiki examples are misaligned regarding to current NixOS versions.

@Sandro Thanks for that advise. The nextcloud config is far too complex (lots of possible error sources). But maybe I find another php-fpm based existing Nixpkg.

After have had a thought on @Sandro s hint, I’ve developed a simplistic php-fpm + nginx configuration which is now working for me.

The big difference is, that php-fpm now runs under the user nginx instead of an dedicated user account.

Another issue on my system was, that the php application I tried to run had a misconfiguration and was looking for an non existing path. But this was not related to the error message I posted recently.

This is my ugly looking but working example code :

{ pkgs, lib, config, ... }:
let
  appUser = "nginx";
  domain = "subdomain.mydomain.com";
  dataDir = "/var/www/${domain}/html";
in {
  networking.firewall.allowedTCPPorts = [ 80 443 ];

  services.phpfpm.pools.${appUser} = {
    user = appUser;
    settings = {
      "listen.owner" = appUser;
      "listen.group" = appUser;
      "listen.mode" = "0600";
      "pm" = "dynamic";
      "pm.max_children" = 75;
      "pm.start_servers" = 10;
      "pm.min_spare_servers" = 5;
      "pm.max_spare_servers" = 20;
      "pm.max_requests" = 500;
      "catch_workers_output" = 1;
    };
  };
  
  services.nginx = {
    enable = true;
    virtualHosts = {
      ${domain} = {
        root = "${dataDir}";

        extraConfig = ''
            index index.php;
        '';

        locations."/" = {
          extraConfig = ''
            # First attempt to serve request as file, then
            # as directory, then fall back to displaying a 404.
            
            try_files $uri $uri/ =404;		
            autoindex on;
          '';
	};

        locations."~ \.php"  = {
          extraConfig = ''
            include ${config.services.nginx.package}/conf/fastcgi_params;

            # regex to split $uri to $fastcgi_script_name and $fastcgi_path
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;

            # Check that the PHP script exists before passing it
            try_files $fastcgi_script_name =404;

            # Bypass the fact that try_files resets $fastcgi_path_info
            # see: http://trac.nginx.org/nginx/ticket/321
            set $path_info $fastcgi_path_info;
            fastcgi_param PATH_INFO $path_info;

            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
            include ${pkgs.nginx}/conf/fastcgi.conf;

            fastcgi_pass unix:${config.services.phpfpm.pools.${appUser}.socket};
          '';
        };
      };
    };
  };
}

Be aware, regarding the fastcgi-params and the locations definitions there are many different flavours existing in the wild. It depends how your php applications expectations. So you may have a look into the applications manual pages, too.

I’ll mention for posterity that running php-fpm as the nginx user has some security implications, for example php scripts now have access to your SSL certificate. Given how php scripts are an extremely common attack vector this isn’t the best possible solution. I would recommend you use a different user if possible.

1 Like

You’re absolutely right! My code snippet is not suitable for an production environment!

But this is where the problem lies - some access control mechanism I have not yet understood causes an error with the Phpfpm - NixOS Wiki example
I’ll elaborate on this.

I’ve done some rework. Now the php-fpm service and nginx each running under distinct users.

{ pkgs, lib, config, ... }:
let
  app = "myApp";
  appDomain = "myapp.com";
  dataDir = "/var/www/${app}/html";
in {

  services.phpfpm.pools.${app} = {
    user = app;
    settings = {
      "listen.owner" = config.services.nginx.user;
      "listen.group" = config.services.nginx.group;
      "listen.mode" = "0660";
      "catch_workers_output" = 1;
    };
  };

  users.groups.${app}.members = [ "${app}" ];
  users.users.${app} = {
    isSystemUser = true;
    group = "${app}";
  };
  users.users.nginx.extraGroups = [ "${app}"];

  services.nginx = {
    enable = true;

    virtualHosts = {
      ${appDomain} = {
        root = "${dataDir}";

        extraConfig = ''
            index index.php;
        '';

        locations."~ ^(.+\\.php)(.*)$"  = {
          extraConfig = ''
            # Check that the PHP script exists before passing it
            try_files $fastcgi_script_name =404;
            include ${config.services.nginx.package}/conf/fastcgi_params;
            fastcgi_split_path_info  ^(.+\.php)(.*)$;
            fastcgi_pass unix:${config.services.phpfpm.pools.${app}.socket};
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO        $fastcgi_path_info;

            include ${pkgs.nginx}/conf/fastcgi.conf;            
          '';
        };
      };
    };
  };
}

@aanderse Do you think this example looks good from a security point of view
I have omitted TLS for simplicity.

1 Like

Great work! Yeah that looks good. Standard user per web app model that NixOS modules promote is looking good here :+1:

2 Likes

I have tried this and failed on my system. It complain about the php-fpm. the file I serve is just phpinfo

if you would like to include some details like your config and the error i can take a look.

it turns out that I miss type the listen.mode from 0660 to 0600. It works now, and I can edit it on the fly for local dev. But it’s still a hassle to change the group owner of a file everytime I add new file. changing listen.group to “users” will fail the php-fpm service

maybe this has to do with some systemd hardening options like PrivateUsers abs ProtectHome that are applied to the service… :thinking:

do you know how to disable those to check?

I have tried to disable ProtectHome and it’s still didn’t work.I have yet tried PrivateUser, but will try it maybe tomorrow. I was planning on using XAMPP like normal development, but there’s no XAMPP or other pre-configure development env for PHP in nixpkgs AFAIK. So I just straight configure Nginx as I would on a production.

you might want to consider looking at GitHub - cachix/devenv: Fast, Declarative, Reproducible, and Composable Developer Environments for your development environment