diff --git a/configuration/default.nix b/configuration/default.nix
index 610851a..14775b7 100644
--- a/configuration/default.nix
+++ b/configuration/default.nix
@@ -137,8 +137,40 @@
     recommendedProxySettings = true;
     clientMaxBodySize = "10G";
     domain = "tlater.net";
+
+    statusPage = true; # For metrics, should be accessible only from localhost
+
+    commonHttpConfig = ''
+      log_format upstream_time '$remote_addr - $remote_user [$time_local] '
+                         '"$request" $status $body_bytes_sent '
+                         '"$http_referer" "$http_user_agent" '
+                         'rt=$request_time uct="$upstream_connect_time" '
+                         'uht="$upstream_header_time" urt="$upstream_response_time"';
+    '';
   };
 
+  services.logrotate = {
+    enable = true;
+
+    settings = lib.mapAttrs' (virtualHost: _:
+      lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
+        frequency = "daily";
+        rotate = 2;
+        compress = true;
+        delaycompress = true;
+        su = "${config.services.nginx.user} ${config.services.nginx.group}";
+        postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
+      })
+    config.services.nginx.virtualHosts;
+  };
+  systemd.tmpfiles.rules =
+    lib.mapAttrsToList (
+      virtualHost: _:
+      #
+      "d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
+    )
+    config.services.nginx.virtualHosts;
+
   security.acme = {
     defaults.email = "tm@tlater.net";
     acceptTerms = true;
diff --git a/configuration/services/conduit.nix b/configuration/services/conduit.nix
index 3f8fd40..dcd0103 100644
--- a/configuration/services/conduit.nix
+++ b/configuration/services/conduit.nix
@@ -205,6 +205,7 @@ in {
     addSSL = true;
     extraConfig = ''
       merge_slashes off;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
     '';
 
     locations = {
diff --git a/configuration/services/foundryvtt.nix b/configuration/services/foundryvtt.nix
index 7bb2286..d573480 100644
--- a/configuration/services/foundryvtt.nix
+++ b/configuration/services/foundryvtt.nix
@@ -25,6 +25,7 @@ in {
     enableACME = true;
     extraConfig = ''
       add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
     '';
 
     locations."/" = {
diff --git a/configuration/services/gitea.nix b/configuration/services/gitea.nix
index 27353f6..6d6dafd 100644
--- a/configuration/services/gitea.nix
+++ b/configuration/services/gitea.nix
@@ -33,6 +33,7 @@ in {
     enableACME = true;
     extraConfig = ''
       add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
     '';
 
     locations."/".proxyPass = "http://${httpAddress}:${toString httpPort}";
diff --git a/configuration/services/metrics.nix b/configuration/services/metrics.nix
index 12a48e0..ad71a98 100644
--- a/configuration/services/metrics.nix
+++ b/configuration/services/metrics.nix
@@ -50,6 +50,37 @@ in {
       enabledCollectors = ["systemd"];
       listenAddress = "127.0.0.1";
     };
+
+    nginx = {
+      enable = true;
+      listenAddress = "127.0.0.1";
+    };
+
+    nginxlog = {
+      enable = true;
+      listenAddress = "127.0.0.1";
+      group = "nginx";
+
+      settings.namespaces =
+        lib.mapAttrsToList (name: virtualHost: {
+          inherit name;
+          metrics_override.prefix = "nginxlog";
+          namespace_label = "vhost";
+
+          format = lib.concatStringsSep " " [
+            "$remote_addr - $remote_user [$time_local]"
+            ''"$request" $status $body_bytes_sent''
+            ''"$http_referer" "$http_user_agent"''
+            ''rt=$request_time uct="$upstream_connect_time"''
+            ''uht="$upstream_header_time" urt="$upstream_response_time"''
+          ];
+
+          source.files = [
+            "/var/log/nginx/${name}/access.log"
+          ];
+        })
+        config.services.nginx.virtualHosts;
+    };
   };
 
   systemd.services.export-to-victoriametrics = let
@@ -80,6 +111,7 @@ in {
     enableACME = true;
     extraConfig = ''
       add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
     '';
     locations."/".proxyPass = "http://localhost:3001";
   };
diff --git a/configuration/services/nextcloud.nix b/configuration/services/nextcloud.nix
index fbca607..81f38a3 100644
--- a/configuration/services/nextcloud.nix
+++ b/configuration/services/nextcloud.nix
@@ -50,6 +50,9 @@ in {
   services.nginx.virtualHosts."${hostName}" = {
     forceSSL = true;
     enableACME = true;
+    extraConfig = ''
+      access_log /var/log/nginx/${hostName}/access.log upstream_time;
+    '';
   };
 
   # Block repeated failed login attempts
diff --git a/configuration/services/webserver.nix b/configuration/services/webserver.nix
index 4a8bee4..085b1f7 100644
--- a/configuration/services/webserver.nix
+++ b/configuration/services/webserver.nix
@@ -19,6 +19,7 @@ in {
     enableACME = true;
     extraConfig = ''
       add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
     '';
 
     locations."/".proxyPass = "http://${addr}:${toString port}";