diff --git a/modules/tv/git/cgit.nix b/modules/tv/git/cgit.nix
index 90661e7da..fd87b9081 100644
--- a/modules/tv/git/cgit.nix
+++ b/modules/tv/git/cgit.nix
@@ -6,10 +6,16 @@ let
 
   cfg = config.services.git;
 
+  location = lib.nameValuePair; # TODO this is also in modules/wu/default.nix
+
   isPublicRepo = getAttr "public"; # TODO this is also in ./default.nix
 in
 
 {
+  imports = [
+    ../../tv/nginx
+  ];
+
   config = mkIf cfg.cgit {
 
     users.extraUsers = lib.singleton {
@@ -66,46 +72,26 @@ in
       '') (filter isPublicRepo (attrValues cfg.repos))}
     '';
 
-    # TODO modular nginx configuration
-    services.nginx =
-      let
-        name = config.networking.hostName;
-        qname = "${name}.retiolum";
-      in
-        {
-          enable = true;
-          httpConfig = ''
-            include           ${pkgs.nginx}/conf/mime.types;
-            default_type      application/octet-stream;
-            sendfile          on;
-            keepalive_timeout 65;
-            gzip              on;
-            server {
-              listen 80;
-              server_name ${name} ${qname} localhost;
-              root ${pkgs.cgit}/cgit;
-
-              location /cgit-static {
-                rewrite ^/cgit-static(/.*)$ $1 break;
-                #expires 30d;
-              }
-
-              location /cgit {
-                include             ${pkgs.nginx}/conf/fastcgi_params;
-                fastcgi_param       SCRIPT_FILENAME $document_root/cgit.cgi;
-                #fastcgi_param       PATH_INFO       $uri;
-                fastcgi_split_path_info ^(/cgit/?)(.+)$;
-                fastcgi_param PATH_INFO $fastcgi_path_info;
-                fastcgi_param       QUERY_STRING    $args;
-                fastcgi_param       HTTP_HOST       $server_name;
-                fastcgi_pass        unix:${config.services.fcgiwrap.socketAddress};
-              }
-
-              location / {
-                return 404;
-              }
-            }
-          '';
-        };
+    tv.nginx = {
+      enable = true;
+      retiolum-locations = [
+        (location "/cgit/" ''
+          include             ${pkgs.nginx}/conf/fastcgi_params;
+          fastcgi_param       SCRIPT_FILENAME ${pkgs.cgit}/cgit/cgit.cgi;
+          fastcgi_split_path_info ^(/cgit/?)(.+)$;
+          fastcgi_param       PATH_INFO       $fastcgi_path_info;
+          fastcgi_param       QUERY_STRING    $args;
+          fastcgi_param       HTTP_HOST       $server_name;
+          fastcgi_pass        unix:${config.services.fcgiwrap.socketAddress};
+        '')
+        (location "= /cgit" ''
+          return 301 /cgit/;
+        '')
+        (location "/cgit-static/" ''
+          root ${pkgs.cgit}/cgit;
+          rewrite ^/cgit-static(/.*)$ $1 break;
+        '')
+      ];
+    };
   };
 }
diff --git a/modules/tv/nginx.nix b/modules/tv/nginx.nix
deleted file mode 100644
index 8b420613b..000000000
--- a/modules/tv/nginx.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{ config, pkgs, ... }:
-
-{
-  services.nginx =
-    let
-      name = config.networking.hostName;
-      qname = "${name}.retiolum";
-    in
-      {
-        enable = true;
-        httpConfig = ''
-          sendfile  on;
-          server {
-            listen      80;
-            server_name ${name} ${qname} localhost;
-            root /srv/http/${name};
-            location ~ ^/~(.+?)(/.*)?$ {
-              alias /home/$1/public_html$2;
-            }
-          }
-          types {
-            text/css css;
-            text/html html;
-            image/svg+xml svg;
-          }
-          default_type text/html;
-          charset utf-8;
-        '';
-      };
-}
diff --git a/modules/tv/nginx/config.nix b/modules/tv/nginx/config.nix
new file mode 100644
index 000000000..e5c3dd152
--- /dev/null
+++ b/modules/tv/nginx/config.nix
@@ -0,0 +1,49 @@
+{ cfg, config, lib, pkgs, ... }:
+
+let
+  inherit (lib) concatStrings replaceChars;
+
+  indent = replaceChars ["\n"] ["\n  "];
+
+  to-location = { name, value }: ''
+    location ${name} {
+      ${indent value}
+    }
+  '';
+in
+
+{
+  services.nginx =
+    let
+      name = config.services.retiolum.name;
+      qname = "${name}.retiolum";
+    in
+    assert config.services.retiolum.enable;
+    {
+      enable = true;
+      httpConfig = ''
+        include           ${pkgs.nginx}/conf/mime.types;
+        default_type      application/octet-stream;
+        sendfile          on;
+        keepalive_timeout 65;
+        gzip              on;
+        server {
+          listen 80 default_server;
+          server_name _;
+          location / {
+            return 404;
+          }
+        }
+        server {
+          listen 80;
+          server_name ${name} ${qname};
+
+          ${indent (concatStrings (map to-location cfg.retiolum-locations))}
+
+          location / {
+            return 404;
+          }
+        }
+      '';
+    };
+}
diff --git a/modules/tv/nginx/default.nix b/modules/tv/nginx/default.nix
new file mode 100644
index 000000000..49133fbb3
--- /dev/null
+++ b/modules/tv/nginx/default.nix
@@ -0,0 +1,11 @@
+arg@{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.tv.nginx;
+  arg' = arg // { inherit cfg; };
+in
+
+{
+  options.tv.nginx = import ./options.nix arg';
+  config = lib.mkIf cfg.enable (import ./config.nix arg');
+}
diff --git a/modules/tv/nginx/options.nix b/modules/tv/nginx/options.nix
new file mode 100644
index 000000000..ddfb38049
--- /dev/null
+++ b/modules/tv/nginx/options.nix
@@ -0,0 +1,21 @@
+{ lib, ... }:
+
+let
+  inherit (lib) mkOption types;
+in
+
+{
+  enable = mkOption {
+    type = types.bool;
+    default = false;
+    description = "Enable nginx.";
+  };
+
+  retiolum-locations = mkOption {
+    type = with types; listOf (attrsOf str);
+    default = [];
+    description = ''
+      TODO
+    '';
+  };
+}
diff --git a/modules/wu/default.nix b/modules/wu/default.nix
index 76e8c6bb3..ac11f7466 100644
--- a/modules/wu/default.nix
+++ b/modules/wu/default.nix
@@ -4,6 +4,8 @@ let
   lib = import ../../lib { lib = pkgs.lib; inherit pkgs; };
 
   inherit (lib) majmin;
+
+  location = pkgs.lib.nameValuePair; # TODO this is also in modules/tv/git/cgit.nix
 in
 
 {
@@ -12,7 +14,6 @@ in
     ../common/nixpkgs.nix
     ../tv/base.nix
     ../tv/exim-retiolum.nix
-    ../tv/nginx.nix
     ../tv/retiolum.nix
     ../tv/sanitize.nix
     ../tv/smartd.nix
@@ -33,6 +34,17 @@ in
         ];
       };
     }
+    {
+      imports = [ ../tv/nginx ];
+      tv.nginx = {
+        enable = true;
+        retiolum-locations = [
+          (location "~ ^/~(.+?)(/.*)?\$" ''
+            alias /home/$1/public_html$2;
+          '')
+        ];
+      };
+    }
   ];
 
   nix.maxJobs = 8;