diff --git a/doc/guix.texi b/doc/guix.texi
index 671cdab6f8..71a00740bc 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -31684,6 +31693,31 @@ valued G-expression.
 @end table
 @end deftp
 
+@deftp {Data Type} nginx-log-format-configuration
+Data type representing the configuration of a custom Nginx log format.
+This type has the following parameters:
+
+@table @asis
+@item @code{name} (type: symbol)
+The name of the log format as a symbol.
+
+@item @code{escape} (default: @code{'default}) (type: symbol)
+The mode used to escape values of variables in the format.  The
+supported values are:
+
+@itemize
+@item @code{'default}, to escape all characters outside of the ASCII
+      printable range.
+@item @code{'json}, to escape all characters invalid in JSON strings.
+@item @code{'none}, to disable escaping.
+@end itemize
+
+@item @code{format} (type: string)
+The format as accepted by the @code{log_format} directive.
+
+@end table
+@end deftp
+
 @anchor{nginx-server-configuration}
 @deftp {Data Type} nginx-server-configuration
 Data type representing the configuration of an nginx server block.
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index 406117c457..5f895bab6d 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -17,6 +17,7 @@
 ;;; Copyright © 2022 Simen Endsjø <simendsjo@gmail.com>
 ;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu>
 ;;; Copyright © 2023 Miguel Ángel Moreno <mail@migalmoreno.com>
+;;; Copyright © 2024 Leo Nikkilä <hello@lnikki.la>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -103,6 +104,8 @@ (define-module (gnu services web)
             nginx-configuration-nginx
             nginx-configuration-shepherd-requirement
             nginx-configuration-log-directory
+            nginx-configuration-log-format
+            nginx-configuration-log-formats
             nginx-configuration-log-level
             nginx-configuration-run-directory
             nginx-configuration-server-blocks
@@ -114,6 +117,12 @@ (define-module (gnu services web)
             nginx-configuration-extra-content
             nginx-configuration-file
 
+            nginx-log-format-configuration
+            nginx-log-format-configuration?
+            nginx-log-format-configuration-name
+            nginx-log-format-configuration-escape
+            nginx-log-format-configuration-format
+
             nginx-server-configuration
             nginx-server-configuration?
             nginx-server-configuration-listen
@@ -522,6 +531,23 @@ (define httpd-service-type
                   (httpd-configuration))
                 (description "Run the Apache httpd Web server.")))
 
+(define-record-type* <nginx-log-format-configuration>
+  nginx-log-format-configuration make-nginx-log-format-configuration
+  nginx-log-format-configuration?
+  (name                nginx-log-format-configuration-name)
+  (escape              nginx-log-format-configuration-escape
+                       (sanitize assert-valid-log-format-escape)
+                       (default 'default))
+  (format              nginx-log-format-configuration-format))
+
+(define (assert-valid-log-format-escape escape)
+  "Ensure @var{escape} is one of @code{'default}, @code{'json}, or
+@code{'none}."
+  (unless (memq escape '(default json none))
+    (raise
+     (formatted-message (G_ "unknown log format escape '~a'~%") escape)))
+  escape)
+
 (define-record-type* <nginx-server-configuration>
   nginx-server-configuration make-nginx-server-configuration
   nginx-server-configuration?
@@ -580,6 +606,10 @@ (define-record-type* <nginx-configuration>
   (log-level     nginx-configuration-log-level
                  (sanitize assert-valid-log-level)
                  (default 'error))
+  (log-format    nginx-configuration-log-format     ;symbol
+                 (default 'combined))
+  (log-formats   nginx-configuration-log-formats    ;list of <nginx-log-format-configuration>
+                 (default '()))
   (run-directory nginx-configuration-run-directory  ;string
                  (default "/var/run/nginx"))
   (server-blocks nginx-configuration-server-blocks
@@ -635,6 +665,12 @@ (define emit-global-directive
     ((key . value)
      (format #f "~a ~a;~%" key value))))
 
+(define emit-nginx-log-format-config
+  (match-lambda
+    (($ <nginx-log-format-configuration> name escape format)
+     (list "    log_format " (symbol->string name) " escape="
+           (symbol->string escape) " " format ";\n"))))
+
 (define emit-nginx-location-config
   (match-lambda
     (($ <nginx-location-configuration> uri body)
@@ -718,7 +754,7 @@ (define (default-nginx-config config)
   (match-record config
                 <nginx-configuration>
                 (nginx log-directory run-directory
-                 log-level
+                 log-level log-format log-formats
                  server-blocks upstream-blocks
                  server-names-hash-bucket-size
                  server-names-hash-bucket-max-size
@@ -740,7 +776,8 @@ (define (default-nginx-config config)
            "    fastcgi_temp_path " run-directory "/fastcgi_temp;\n"
            "    uwsgi_temp_path " run-directory "/uwsgi_temp;\n"
            "    scgi_temp_path " run-directory "/scgi_temp;\n"
-           "    access_log " log-directory "/access.log;\n"
+           (map emit-nginx-log-format-config log-formats)
+           "    access_log " log-directory "/access.log " (symbol->string log-format) ";\n"
            "    include " nginx "/share/nginx/conf/mime.types;\n"
            (if lua-package-path
                #~(format #f "    lua_package_path ~s;~%"
