outer@{
  lib,
  stdenv,
  fetchurl,
  fetchpatch,
  openssl,
  zlib-ng,
  zlib,
  pcre2,
  libxml2,
  libxslt,
  nginx-doc,

  nixosTests,
  installShellFiles,
  replaceVars,
  removeReferencesTo,
  gd,
  geoip,
  perl,
  withDebug ? false,
  withGeoIP ? false,
  withImageFilter ? false,
  withKTLS ? true,
  withStream ? true,
  withMail ? false,
  withPerl ? true,
  withSlice ? false,
  modules ? [ ],
  ...
}:

{
  pname ? "nginx",
  version,
  nginxVersion ? version,
  src ? null, # defaults to upstream nginx ${version}
  hash ? null, # when not specifying src
  configureFlags ? [ ],
  nativeBuildInputs ? [ ],
  buildInputs ? [ ],
  extraPatches ? [ ],
  fixPatch ? p: p,
  postPatch ? null,
  preConfigure ? "",
  preInstall ? "",
  postInstall ? "",
  meta ? null,
  nginx-doc ? outer.nginx-doc,
  passthru ? {
    tests = { };
  },
}:

let

  moduleNames = map (
    mod:
    mod.name
      or (throw "The nginx module with source ${toString mod.src} does not have a `name` attribute. This prevents duplicate module detection and is no longer supported.")
  ) modules;

  moduleSource = mod:
    if (mod.src ? patches || mod.src ? patchPhase || mod.src ? prePatch || mod.src ? postPatch) then
    stdenv.mkDerivation ({
      name = "source";
      src = mod.src;
      installPhase = "cp -r . $out";
    } // (lib.filterAttrs (n: v: (lib.elem n ["patches" "patchPhase" "prePatch" "postPatch"])) mod.src))
    else
    mod.src;

  mapModules =
    attrPath:
    lib.flip lib.concatMap modules (
      mod:
      let
        supports = mod.supports or (_: true);
      in
      if supports nginxVersion then
        mod.${attrPath} or [ ]
      else
        throw "Module at ${toString mod.src} does not support nginx version ${nginxVersion}!"
    );

in

assert lib.assertMsg (lib.unique moduleNames == moduleNames)
  "nginx: duplicate modules: ${lib.concatStringsSep ", " moduleNames}. A common cause for this is that services.nginx.additionalModules adds a module which the nixos module itself already adds.";

stdenv.mkDerivation {
  inherit pname version nginxVersion;

  outputs = [
    "out"
    "doc"
  ];

  src =
    if src != null then
      src
    else
      fetchurl {
        url = "https://nginx.org/download/nginx-${version}.tar.gz";
        inherit hash;
      };

  nativeBuildInputs = [
    installShellFiles
    removeReferencesTo
  ]
  ++ nativeBuildInputs;

  buildInputs = [
    openssl
    zlib-ng
    # fix: configure: error: the HTTP gzip module requires the zlib library
    zlib
    pcre2
    libxml2
    libxslt
    perl
  ]
  ++ buildInputs
  ++ mapModules "inputs"
  ++ mapModules "buildInputs"
  ++ lib.optional withGeoIP geoip
  ++ lib.optional withImageFilter gd;

  configureFlags = [
    "--sbin-path=bin/nginx"
    "--with-http_ssl_module"
    "--with-http_v2_module"
    "--with-http_v3_module"
    "--with-http_realip_module"
    "--with-http_addition_module"
    "--with-http_xslt_module"
    "--with-http_sub_module"
    "--with-http_dav_module"
    "--with-http_flv_module"
    "--with-http_mp4_module"
    "--with-http_gunzip_module"
    "--with-http_gzip_static_module"
    "--with-http_auth_request_module"
    "--with-http_random_index_module"
    "--with-http_secure_link_module"
    "--with-http_degradation_module"
    "--with-http_stub_status_module"
    "--with-threads"
    "--with-pcre-jit"
    "--http-log-path=/var/log/nginx/access.log"
    "--error-log-path=/var/log/nginx/error.log"
    "--pid-path=/var/log/nginx/nginx.pid"
    "--http-client-body-temp-path=/tmp/nginx_client_body"
    "--http-proxy-temp-path=/tmp/nginx_proxy"
    "--http-fastcgi-temp-path=/tmp/nginx_fastcgi"
    "--http-uwsgi-temp-path=/tmp/nginx_uwsgi"
    "--http-scgi-temp-path=/tmp/nginx_scgi"
  ]
  ++ lib.optionals withDebug [
    "--with-debug"
  ]
  ++ lib.optionals withKTLS [
    "--with-openssl-opt=enable-ktls"
  ]
  ++ lib.optionals withStream [
    "--with-stream"
    "--with-stream_realip_module"
    "--with-stream_ssl_module"
    "--with-stream_ssl_preread_module"
  ]
  ++ lib.optionals withMail [
    "--with-mail"
    "--with-mail_ssl_module"
  ]
  ++ lib.optionals withPerl [
    "--with-http_perl_module"
    "--with-perl=${perl}/bin/perl"
    "--with-perl_modules_path=lib/perl5"
  ]
  ++ lib.optional withImageFilter "--with-http_image_filter_module"
  ++ lib.optional withSlice "--with-http_slice_module"
  ++ lib.optionals withGeoIP (
    [ "--with-http_geoip_module" ] ++ lib.optional withStream "--with-stream_geoip_module"
  )
  ++ lib.optional (with stdenv.hostPlatform; isLinux || isFreeBSD) "--with-file-aio"
  ++ lib.optional (
    stdenv.buildPlatform != stdenv.hostPlatform
  ) "--crossbuild=${stdenv.hostPlatform.uname.system}::${stdenv.hostPlatform.uname.processor}"
  ++ configureFlags
  ++ mapModules "configureFlags"
  ++ map (mod: "--add-module=${moduleSource mod}") modules;

  env = {
    NIX_CFLAGS_COMPILE = toString (
      [
        "-I${libxml2.dev}/include/libxml2"
        "-Wno-error=implicit-fallthrough"
        (
          # zlig-ng patch needs this
          if stdenv.cc.isGNU then
            "-Wno-error=discarded-qualifiers"
          else
            "-Wno-error=incompatible-pointer-types-discards-qualifiers"
        )
      ]
      ++ lib.optionals (stdenv.cc.isGNU && lib.versionAtLeast stdenv.cc.version "11") [
        # fix build vts module on gcc11
        "-Wno-error=stringop-overread"
      ]
      ++ lib.optionals stdenv.cc.isClang [
        "-Wno-error=deprecated-declarations"
        "-Wno-error=gnu-folding-constant"
        "-Wno-error=unused-but-set-variable"
      ]
      ++ lib.optionals stdenv.hostPlatform.isMusl [
        # fix sys/cdefs.h is deprecated
        "-Wno-error=cpp"
      ]
    );
  }
  // lib.optionalAttrs (stdenv.buildPlatform != stdenv.hostPlatform) {
    CONFIG_BIG_ENDIAN = if stdenv.hostPlatform.isBigEndian then "y" else "n";
  };

  configurePlatforms = [ ];

  # Disable _multioutConfig hook which adds --bindir=$out/bin into configureFlags,
  # which breaks build, since nginx does not actually use autoconf.
  preConfigure = ''
    setOutputFlags=
  ''
  + preConfigure
  + lib.concatMapStringsSep "\n" (mod: mod.preConfigure or "") modules;

  patches =
    map fixPatch (
      [
        ./nix-etag-1.15.4.patch
        ./nix-skip-check-logs-path.patch
      ]
      # Upstream may be against cross-compilation patches.
      # https://trac.nginx.org/nginx/ticket/2240 https://trac.nginx.org/nginx/ticket/1928#comment:6
      # That dev quit the project in 2024 so the stance could be different now.
      ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
        (fetchpatch {
          url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/102-sizeof_test_fix.patch";
          sha256 = "0i2k30ac8d7inj9l6bl0684kjglam2f68z8lf3xggcc2i5wzhh8a";
        })
        (fetchpatch {
          url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/101-feature_test_fix.patch";
          sha256 = "0v6890a85aqmw60pgj3mm7g8nkaphgq65dj4v9c6h58wdsrc6f0y";
        })
        (fetchpatch {
          url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/103-sys_nerr.patch";
          sha256 = "0s497x6mkz947aw29wdy073k8dyjq8j99lax1a1mzpikzr4rxlmd";
        })
        (fetchpatch {
          url = "https://raw.githubusercontent.com/openwrt/packages/c057dfb09c7027287c7862afab965a4cd95293a3/net/nginx/patches/104-endianness_fix.patch";
          sha256 = "sha256-M7V3ZJfKImur2OoqXcoL+CbgFj/huWnfZ4xMCmvkqfc=";
        })
      ]
      ++ mapModules "patches"
    )
    ++ extraPatches;

  postPatch = lib.defaultTo ''
    substituteInPlace src/http/ngx_http_core_module.c \
      --replace-fail '@nixStoreDir@' "$NIX_STORE" \
      --replace-fail '@nixStoreDirLen@' "''${#NIX_STORE}"
  '' postPatch;

  enableParallelBuilding = true;

  preInstall = ''
    mkdir -p $doc
    cp -r ${nginx-doc}/* $doc

    # TODO: make it unconditional when `openresty` and `nginx` are not
    # sharing this code.
    if [[ -e man/nginx.8 ]]; then
      installManPage man/nginx.8
    fi
  ''
  + preInstall;

  disallowedReferences = map (m: m.src) modules;

  postInstall =
    let
      noSourceRefs = lib.concatMapStrings (
        m: "remove-references-to -t ${m.src} $(readlink -fn $out/bin/nginx)\n"
      ) modules;
    in
    postInstall + noSourceRefs;

  passthru = {
    inherit modules;
    tests = {
      inherit (nixosTests)
        nginx
        nginx-auth
        nginx-etag
        nginx-etag-compression
        nginx-globalredirect
        nginx-http3
        nginx-proxyprotocol
        nginx-pubhtml
        nginx-sso
        nginx-status-page
        nginx-unix-socket
        ;
      variants = lib.recurseIntoAttrs nixosTests.nginx-variants;
      acme-integration = nixosTests.acme.nginx;
      acme-integration-without-reload = nixosTests.acme.nginx-without-reload;
    }
    // passthru.tests;
  };

  meta =
    if meta != null then
      meta
    else
      {
        description = "Reverse proxy and lightweight webserver";
        mainProgram = "nginx";
        homepage = "http://nginx.org";
        license = [ lib.licenses.bsd2 ] ++ lib.concatMap (m: lib.toList m.meta.license) modules;
        broken = lib.any (m: m.meta.broken or false) modules;
        platforms = lib.platforms.all;
        maintainers = with lib.maintainers; [
          das_j
          fpletz
          helsinki-Jo
          raitobezarius
        ];
      };
}
