diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index 4f962eadd..65b2545b0 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -1542,6 +1542,16 @@ in
               programs.ssh.matchBlocks.<name>.serverAliveCountMax
         '';
       }
+
+      {
+        time = "2020-06-11T18:06:37+00:00";
+        condition = config.services.emacs.enable;
+        message = ''
+          The Emacs service now supports systemd socket activation.
+
+          It can be enabled through the option 'services.emacs.socketActivation.enable'.
+        '';
+      }
     ];
   };
 }
diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix
index c02762625..7b91bcc29 100644
--- a/modules/services/emacs.nix
+++ b/modules/services/emacs.nix
@@ -7,6 +7,8 @@ let
   cfg = config.services.emacs;
   emacsCfg = config.programs.emacs;
   emacsBinPath = "${emacsCfg.finalPackage}/bin";
+  emacsVersion = getVersion emacsCfg.finalPackage;
+
   # Adapted from upstream emacs.desktop
   clientDesktopItem = pkgs.makeDesktopItem rec {
     name = "emacsclient";
@@ -27,9 +29,26 @@ let
     '';
   };
 
+  # Match the default socket path for the Emacs version so emacsclient continues
+  # to work without wrapping it. It might be worthwhile to allow customizing the
+  # socket path, but we would want to wrap emacsclient in the user profile to
+  # connect to the alternative socket by default for Emacs 26, and set
+  # EMACS_SOCKET_NAME for Emacs 27.
+  #
+  # As systemd doesn't perform variable expansion for the ListenStream param, we
+  # would also have to solve the problem of matching the shell path to the path
+  # used in the socket unit, which would likely involve templating. It seems of
+  # little value for the most common use case of one Emacs daemon per user
+  # session.
+  socketPath = if versionAtLeast emacsVersion "27" then
+    "%t/emacs/server"
+  else
+    "%T/emacs%U/server";
+
 in {
   options.services.emacs = {
     enable = mkEnableOption "the Emacs daemon";
+
     client = {
       enable = mkEnableOption "generation of Emacs client desktop file";
       arguments = mkOption {
@@ -40,36 +59,78 @@ in {
         '';
       };
     };
-  };
 
-  config = mkIf cfg.enable {
-    assertions = [{
-      assertion = emacsCfg.enable;
-      message = "The Emacs service module requires"
-        + " 'programs.emacs.enable = true'.";
-    }];
-
-    systemd.user.services.emacs = {
-      Unit = {
-        Description = "Emacs: the extensible, self-documenting text editor";
-        Documentation =
-          "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
-
-        # Avoid killing the Emacs session, which may be full of
-        # unsaved buffers.
-        X-RestartIfChanged = false;
-      };
-
-      Service = {
-        ExecStart =
-          "${pkgs.runtimeShell} -l -c 'exec ${emacsBinPath}/emacs --fg-daemon'";
-        ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs)'";
-        Restart = "on-failure";
-      };
-
-      Install = { WantedBy = [ "default.target" ]; };
+    # Attrset for forward-compatibility; there may be a need to customize the
+    # socket path, though allowing for such is not easy to do as systemd socket
+    # units don't perform variable expansion for 'ListenStream'.
+    socketActivation = {
+      enable = mkEnableOption "systemd socket activation for the Emacs service";
     };
-
-    home.packages = optional cfg.client.enable clientDesktopItem;
   };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      assertions = [
+        {
+          assertion = emacsCfg.enable;
+          message = "The Emacs service module requires"
+            + " 'programs.emacs.enable = true'.";
+        }
+        {
+          assertion = cfg.socketActivation.enable
+            -> versionAtLeast emacsVersion "26";
+          message = "Socket activation requires Emacs 26 or newer.";
+        }
+      ];
+
+      systemd.user.services.emacs = {
+        Unit = {
+          Description = "Emacs: the extensible, self-documenting text editor";
+          Documentation =
+            "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+
+          # Avoid killing the Emacs session, which may be full of
+          # unsaved buffers.
+          X-RestartIfChanged = false;
+        };
+
+        Service = {
+          ExecStart = "${emacsBinPath}/emacs --fg-daemon${
+            # In case the user sets 'server-directory' or 'server-name' in
+            # their Emacs config, we want to specify the socket path explicitly
+            # so launching 'emacs.service' manually doesn't break emacsclient
+            # when using socket activation.
+              optionalString cfg.socketActivation.enable ''="${socketPath}"''
+            }";
+          # We use '(kill-emacs 0)' to avoid exiting with a failure code, which
+          # would restart the service immediately.
+          ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'";
+          Restart = "on-failure";
+        };
+      } // optionalAttrs (!cfg.socketActivation.enable) {
+        Install = { WantedBy = [ "default.target" ]; };
+      };
+
+      home.packages = optional cfg.client.enable clientDesktopItem;
+    }
+
+    (mkIf cfg.socketActivation.enable {
+      systemd.user.sockets.emacs = {
+        Unit = {
+          Description = "Emacs: the extensible, self-documenting text editor";
+          Documentation =
+            "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+        };
+
+        Socket = {
+          ListenStream = socketPath;
+          FileDescriptorName = "server";
+          SocketMode = "0600";
+          DirectoryMode = "0700";
+        };
+
+        Install = { WantedBy = [ "sockets.target" ]; };
+      };
+    })
+  ]);
 }
diff --git a/tests/default.nix b/tests/default.nix
index 2fbbb5fb4..e3d50511a 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -67,6 +67,7 @@ import nmt {
     ./modules/misc/xsession
     ./modules/programs/abook
     ./modules/programs/autorandr
+    ./modules/services/emacs
     ./modules/programs/firefox
     ./modules/programs/getmail
     ./modules/services/lieer
diff --git a/tests/modules/services/emacs/default.nix b/tests/modules/services/emacs/default.nix
new file mode 100644
index 000000000..af27538d9
--- /dev/null
+++ b/tests/modules/services/emacs/default.nix
@@ -0,0 +1,5 @@
+{
+  emacs-service = ./emacs-service.nix;
+  emacs-socket-26 = ./emacs-socket-26.nix;
+  emacs-socket-27 = ./emacs-socket-27.nix;
+}
diff --git a/tests/modules/services/emacs/emacs-emacsclient.desktop b/tests/modules/services/emacs/emacs-emacsclient.desktop
new file mode 100644
index 000000000..ab9849bb6
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-emacsclient.desktop
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Type=Application
+Exec=@emacs@/bin/emacsclient -c %F
+Terminal=false
+Name=Emacs Client
+Icon=emacs
+Comment=Edit text
+GenericName=Text Editor
+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+Categories=Utility;TextEditor;
+StartupWMClass=Emacs
+
diff --git a/tests/modules/services/emacs/emacs-service-emacs.service b/tests/modules/services/emacs/emacs-service-emacs.service
new file mode 100644
index 000000000..c862e5688
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-service-emacs.service
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=default.target
+
+[Service]
+ExecStart=@emacs@/bin/emacs --fg-daemon
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-service.nix b/tests/modules/services/emacs/emacs-service.nix
new file mode 100644
index 000000000..06a26c823
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-service.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    nixpkgs.overlays = [
+      (self: super: rec {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs" "" // {
+          outPath = "@emacs@";
+        };
+        emacsPackagesFor = _:
+          makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+      })
+    ];
+
+    programs.emacs.enable = true;
+    services.emacs.enable = true;
+    services.emacs.client.enable = true;
+
+    nmt.script = ''
+      assertPathNotExists home-files/.config/systemd/user/emacs.socket
+      assertFileExists home-files/.config/systemd/user/emacs.service
+      assertFileExists home-path/share/applications/emacsclient.desktop
+
+      assertFileContent home-files/.config/systemd/user/emacs.service \
+                        ${./emacs-service-emacs.service}
+      assertFileContent home-path/share/applications/emacsclient.desktop \
+                        ${./emacs-emacsclient.desktop}
+    '';
+  };
+}
diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.service b/tests/modules/services/emacs/emacs-socket-26-emacs.service
new file mode 100644
index 000000000..8e9daba80
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-26-emacs.service
@@ -0,0 +1,9 @@
+[Service]
+ExecStart=@emacs@/bin/emacs --fg-daemon="%T/emacs%U/server"
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.socket b/tests/modules/services/emacs/emacs-socket-26-emacs.socket
new file mode 100644
index 000000000..d2fa78e22
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-26-emacs.socket
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=sockets.target
+
+[Socket]
+DirectoryMode=0700
+FileDescriptorName=server
+ListenStream=%T/emacs%U/server
+SocketMode=0600
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
diff --git a/tests/modules/services/emacs/emacs-socket-26.nix b/tests/modules/services/emacs/emacs-socket-26.nix
new file mode 100644
index 000000000..7905e19b3
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-26.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    nixpkgs.overlays = [
+      (self: super: rec {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-26.3" "" // {
+          outPath = "@emacs@";
+        };
+        emacsPackagesFor = _:
+          makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+      })
+    ];
+
+    programs.emacs.enable = true;
+    services.emacs.enable = true;
+    services.emacs.client.enable = true;
+    services.emacs.socketActivation.enable = true;
+
+    nmt.script = ''
+      assertFileExists home-files/.config/systemd/user/emacs.socket
+      assertFileExists home-files/.config/systemd/user/emacs.service
+      assertFileExists home-path/share/applications/emacsclient.desktop
+
+      assertFileContent home-files/.config/systemd/user/emacs.socket \
+                        ${./emacs-socket-26-emacs.socket}
+      assertFileContent home-files/.config/systemd/user/emacs.service \
+                        ${./emacs-socket-26-emacs.service}
+      assertFileContent home-path/share/applications/emacsclient.desktop \
+                        ${./emacs-emacsclient.desktop}
+    '';
+  };
+}
diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.service b/tests/modules/services/emacs/emacs-socket-27-emacs.service
new file mode 100644
index 000000000..99bacf290
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-27-emacs.service
@@ -0,0 +1,9 @@
+[Service]
+ExecStart=@emacs@/bin/emacs --fg-daemon="%t/emacs/server"
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.socket b/tests/modules/services/emacs/emacs-socket-27-emacs.socket
new file mode 100644
index 000000000..8fa68bf59
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-27-emacs.socket
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=sockets.target
+
+[Socket]
+DirectoryMode=0700
+FileDescriptorName=server
+ListenStream=%t/emacs/server
+SocketMode=0600
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
diff --git a/tests/modules/services/emacs/emacs-socket-27.nix b/tests/modules/services/emacs/emacs-socket-27.nix
new file mode 100644
index 000000000..d1ecb954a
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-socket-27.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+in {
+  config = {
+    nixpkgs.overlays = [
+      (self: super: rec {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-27.0.91" "" // {
+          outPath = "@emacs@";
+        };
+        emacsPackagesFor = _:
+          makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+      })
+    ];
+
+    programs.emacs.enable = true;
+    services.emacs.enable = true;
+    services.emacs.client.enable = true;
+    services.emacs.socketActivation.enable = true;
+
+    nmt.script = ''
+      assertFileExists home-files/.config/systemd/user/emacs.socket
+      assertFileExists home-files/.config/systemd/user/emacs.service
+      assertFileExists home-path/share/applications/emacsclient.desktop
+
+      assertFileContent home-files/.config/systemd/user/emacs.socket \
+                        ${./emacs-socket-27-emacs.socket}
+      assertFileContent home-files/.config/systemd/user/emacs.service \
+                        ${./emacs-socket-27-emacs.service}
+      assertFileContent home-path/share/applications/emacsclient.desktop \
+                        ${./emacs-emacsclient.desktop}
+    '';
+  };
+}