diff --git a/flake.nix b/flake.nix
index 3f8151a..3777ffb 100644
--- a/flake.nix
+++ b/flake.nix
@@ -166,6 +166,7 @@
         hercules-ci = ./modules/nixos/hercules-ci.nix;
         hydra = ./modules/nixos/hydra.nix;
         monitoring = ./modules/nixos/monitoring;
+        nginx = ./modules/nixos/nginx.nix;
         nur-update = ./modules/nixos/nur-update.nix;
         remote-builder = ./modules/nixos/remote-builder.nix;
         watch-store = ./modules/nixos/watch-store.nix;
diff --git a/hosts/build02/default.nix b/hosts/build02/default.nix
index fb35084..2bbc8d9 100644
--- a/hosts/build02/default.nix
+++ b/hosts/build02/default.nix
@@ -2,7 +2,7 @@
 
 {
   imports = [
-    inputs.srvos.nixosModules.mixins-nginx
+    inputs.self.nixosModules.nginx
     inputs.srvos.nixosModules.hardware-hetzner-online-amd
     ./nixpkgs-update.nix
     ./nixpkgs-update-backup.nix
diff --git a/hosts/build02/nixpkgs-update.nix b/hosts/build02/nixpkgs-update.nix
index 69d32bc..4308d67 100644
--- a/hosts/build02/nixpkgs-update.nix
+++ b/hosts/build02/nixpkgs-update.nix
@@ -292,8 +292,6 @@ in
   services.nginx.recommendedZstdSettings = false;
 
   services.nginx.virtualHosts."nixpkgs-update-logs.nix-community.org" = {
-    forceSSL = true;
-    enableACME = true;
     locations."/" = {
       alias = "/var/log/nixpkgs-update/";
       extraConfig = ''
@@ -305,8 +303,6 @@ in
 
   # TODO: permanent redirect r.ryantm.com/log/ -> nixpkgs-update-logs.nix-community.org
   services.nginx.virtualHosts."r.ryantm.com" = {
-    forceSSL = true;
-    enableACME = true;
     locations."/log/" = {
       alias = "/var/log/nixpkgs-update/";
       extraConfig = ''
diff --git a/hosts/build03/default.nix b/hosts/build03/default.nix
index adb115c..17e984b 100644
--- a/hosts/build03/default.nix
+++ b/hosts/build03/default.nix
@@ -1,7 +1,7 @@
 { inputs, ... }:
 {
   imports = [
-    inputs.srvos.nixosModules.mixins-nginx
+    inputs.self.nixosModules.nginx
     inputs.srvos.nixosModules.hardware-hetzner-online-amd
     inputs.self.nixosModules.disko-zfs
     inputs.self.nixosModules.buildbot
diff --git a/hosts/web02/default.nix b/hosts/web02/default.nix
index 38bc3d6..e646446 100644
--- a/hosts/web02/default.nix
+++ b/hosts/web02/default.nix
@@ -3,7 +3,7 @@
   imports = [
     ./gandi.nix
     inputs.self.nixosModules.monitoring
-    inputs.srvos.nixosModules.mixins-nginx
+    inputs.self.nixosModules.nginx
   ];
 
   networking.useDHCP = true;
diff --git a/modules/nixos/buildbot.nix b/modules/nixos/buildbot.nix
index 25f6191..2034ec9 100644
--- a/modules/nixos/buildbot.nix
+++ b/modules/nixos/buildbot.nix
@@ -9,10 +9,7 @@
     inputs.buildbot-nix.nixosModules.buildbot-worker
   ];
 
-  services.nginx.virtualHosts."buildbot.nix-community.org" = {
-    enableACME = true;
-    forceSSL = true;
-  };
+  services.nginx.virtualHosts."buildbot.nix-community.org" = { };
 
   sops.secrets.buildbot-github-oauth-secret = { };
   sops.secrets.buildbot-github-app-secret-key = { };
diff --git a/modules/nixos/hydra.nix b/modules/nixos/hydra.nix
index d9cb762..6c39f82 100644
--- a/modules/nixos/hydra.nix
+++ b/modules/nixos/hydra.nix
@@ -51,12 +51,8 @@
       '';
     };
 
-    services.nginx.virtualHosts = {
-      "hydra.nix-community.org" = {
-        forceSSL = true;
-        enableACME = true;
-        locations."/".proxyPass = "http://localhost:${toString config.services.hydra.port}";
-      };
+    services.nginx.virtualHosts."hydra.nix-community.org" = {
+      locations."/".proxyPass = "http://localhost:${toString config.services.hydra.port}";
     };
 
     # Create user accounts
diff --git a/modules/nixos/monitoring/default.nix b/modules/nixos/monitoring/default.nix
index 174a337..dd41c54 100644
--- a/modules/nixos/monitoring/default.nix
+++ b/modules/nixos/monitoring/default.nix
@@ -11,8 +11,6 @@
   sops.secrets.nginx-basic-auth-file.owner = "nginx";
 
   services.nginx.virtualHosts."monitoring.nix-community.org" = {
-    enableACME = true;
-    forceSSL = true;
     locations."/".return = "302 https://nix-community.org/monitoring";
     locations."/alertmanager/" = {
       basicAuthFile = config.sops.secrets.nginx-basic-auth-file.path;
diff --git a/modules/nixos/nginx.nix b/modules/nixos/nginx.nix
new file mode 100644
index 0000000..9662e8d
--- /dev/null
+++ b/modules/nixos/nginx.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  inputs,
+  lib,
+  pkgs,
+  ...
+}:
+{
+  options.services.nginx.virtualHosts = lib.mkOption {
+    type = lib.types.attrsOf (
+      lib.types.submodule {
+        config = {
+          enableACME = lib.mkDefault true;
+          forceSSL = lib.mkDefault true;
+          kTLS = true;
+
+          extraConfig = ''
+            add_header X-Robots-Tag "none, noarchive, nosnippet";
+          '';
+
+          locations."= /robots.txt".alias = pkgs.writeText "robots.txt" ''
+            User-agent: *
+            Disallow: /
+          '';
+        };
+      }
+    );
+  };
+
+  imports = [ inputs.srvos.nixosModules.mixins-nginx ];
+
+  config = {
+    services.nginx = {
+      appendConfig = ''
+        pcre_jit on;
+        worker_processes auto;
+        worker_cpu_affinity auto;
+      '';
+
+      virtualHosts."${config.networking.hostName}.nix-community.org" = {
+        default = true;
+        locations."/".return = "404";
+        reuseport = true; # should only be set for one virtualHost
+      };
+
+      # localhost is used by the nginx status page
+      virtualHosts.localhost = {
+        enableACME = false;
+        forceSSL = false;
+      };
+    };
+  };
+}
diff --git a/modules/nixos/nur-update.nix b/modules/nixos/nur-update.nix
index 16a7b3d..531838e 100644
--- a/modules/nixos/nur-update.nix
+++ b/modules/nixos/nur-update.nix
@@ -7,8 +7,6 @@
 
 {
   services.nginx.virtualHosts."nur-update.nix-community.org" = {
-    enableACME = true;
-    forceSSL = true;
     locations."/".proxyPass = "http://unix:/run/nur-update/gunicorn.sock";
   };