diff --git a/CMakeLists.txt b/CMakeLists.txt
index a633b94826072f9a0aa13f488abf9249486fe893..039b1865eb51062a158a0044b21fdb35da1d937a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,6 +74,8 @@ cmake_dependent_option(BUILD_WITH_DBUS "Enable dbus support (for BLE support)" O
 cmake_dependent_option(BUILD_COMPOSITOR_MAIN "Build main compositor host" ON "BUILD_WITH_WAYLAND OR BUILD_WITH_XCB" OFF)
 cmake_dependent_option(XRT_BUILD_IPC "Build OpenXR runtime target" ON "BUILD_COMPOSITOR_MAIN" OFF)
 cmake_dependent_option(XRT_HAVE_SYSTEMD "Enable systemd support (for socket activation of service)" ON "Systemd_FOUND AND XRT_BUILD_IPC" OFF)
+cmake_dependent_option(XRT_INSTALL_SYSTEMD_UNIT_FILES "Install user unit files for systemd socket activation on installation" ON "XRT_HAVE_SYSTEMD" OFF)
+cmake_dependent_option(XRT_INSTALL_ABSOLUTE_SYSTEMD_UNIT_FILES "Use an absolute path to monado-system in installed user unit files for systemd socket activation" ON "XRT_INSTALL_SYSTEMD_UNIT_FILES" OFF)
 cmake_dependent_option(BUILD_TARGET_OPENXR "Build OpenXR runtime target" ON "BUILD_COMPOSITOR_MAIN OR XRT_BUILD_IPC" OFF)
 
 # Most users won't touch these.
diff --git a/doc/changes/big/mr.306.md b/doc/changes/big/mr.306.md
new file mode 100644
index 0000000000000000000000000000000000000000..61d6272a2c52753a46ae3f5f39c6369f2bdfb791
--- /dev/null
+++ b/doc/changes/big/mr.306.md
@@ -0,0 +1 @@
+Support optional systemd socket-activation: if not disabled at configure time, `monado-service` can be launched by systemd as a service with an associated socket. If the service is launched this way, it will use the systemd-created domain socket instead of creating its own. (If launched manually, it will still create its own as normal.) This allows optional auto-launching of the service when running a client (OpenXR) application. Associated systemd unit files are also included.
diff --git a/src/xrt/targets/service/CMakeLists.txt b/src/xrt/targets/service/CMakeLists.txt
index f36d54820ef0df14a329c0dc5918fea0574690e0..37ee300a8f3c103046d637d6606d6259351bda0f 100644
--- a/src/xrt/targets/service/CMakeLists.txt
+++ b/src/xrt/targets/service/CMakeLists.txt
@@ -18,3 +18,63 @@ target_link_libraries(monado-service PRIVATE
 install(TARGETS monado-service
 	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
 	)
+
+if(XRT_HAVE_SYSTEMD)
+	set(SERVICE_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/monado.in.service)
+	set(SOCKET_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/monado.in.socket)
+	###
+	# Generate systemd unit files with absolute path to service intended for development without installing
+	set(UNIT_NAME monado-dev)
+	set(service_path $<TARGET_FILE:monado-service>)
+	set(conflicts monado)
+	set(exit_on_disconnect ON)
+	set(extra_desc "in build tree")
+	configure_file(${SOCKET_INPUT} ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}.socket)
+	# Need this step because file(GENERATE) only evaluates generator expressions, and not what configure_file does.
+	configure_file(${SERVICE_INPUT} ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}-intermediate.service)
+	file(GENERATE
+		OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}.service"
+		INPUT ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}-intermediate.service)
+
+	configure_file(${SOCKET_INPUT} ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}.socket @ONLY)
+
+	# Make a custom target to link those in.
+	add_custom_target(link-systemd-dev-units
+		COMMAND systemctl --user link ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}.socket
+		COMMAND systemctl --user link ${CMAKE_CURRENT_BINARY_DIR}/${UNIT_NAME}.service
+		COMMAND systemctl --user daemon-reload
+		VERBATIM
+		COMMENT "Linking monado-dev.{socket,service} into your local systemd unit directory."
+	)
+endif()
+
+if(XRT_INSTALL_SYSTEMD_UNIT_FILES)
+	set(UNIT_NAME monado)
+	set(conflicts monado-dev)
+	set(exit_on_disconnect OFF)
+
+	# Try to ask where to install it
+	pkg_get_variable(PC_SYSTEMD_USERUNITDIR systemd systemduserunitdir)
+	pkg_get_variable(PC_SYSTEMD_PREFIX systemd prefix)
+	if(NOT DEFINED XRT_SYSTEMD_UNIT_INSTALL_DIR)
+		# Fallback dest
+		set(XRT_SYSTEMD_UNIT_INSTALL_DIR lib/systemd/user)
+		if(PC_SYSTEMD_USERUNITDIR AND PC_SYSTEMD_PREFIX)
+			# Strip prefix
+			string(REGEX REPLACE "^${PC_SYSTEMD_PREFIX}/" "" XRT_SYSTEMD_UNIT_INSTALL_DIR "${PC_SYSTEMD_USERUNITDIR}")
+		endif()
+		set(XRT_SYSTEMD_UNIT_INSTALL_DIR "${XRT_SYSTEMD_UNIT_INSTALL_DIR}" CACHE STRING "The (absolute, or CMAKE_INSTALL_PREFIX-relative) path to install the systemd user unit files.")
+		mark_as_advanced(XRT_SYSTEMD_UNIT_INSTALL_DIR)
+	endif()
+	if(XRT_SYSTEMD_UNIT_INSTALL_DIR MATCHES "^/")
+		# Destination is absolute: prepend only destdir at install time
+		set(UNIT_DIR "\$ENV{DESTDIR}${XRT_SYSTEMD_UNIT_INSTALL_DIR}")
+	else()
+		# Destination is relative: prepend destdir and install prefix at install time
+		set(UNIT_DIR "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${XRT_SYSTEMD_UNIT_INSTALL_DIR}")
+	endif()
+	configure_file(configure_and_install_units.cmake ${CMAKE_CURRENT_BINARY_DIR}/configure_and_install_units.cmake @ONLY)
+
+	# This script will configure the units and install them at install time.
+	install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/configure_and_install_units.cmake)
+endif()
diff --git a/src/xrt/targets/service/configure_and_install_units.cmake b/src/xrt/targets/service/configure_and_install_units.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..e58681e7d1e836c4ee3d4a351efcd51869acdeed
--- /dev/null
+++ b/src/xrt/targets/service/configure_and_install_units.cmake
@@ -0,0 +1,26 @@
+# Copyright 2020, Collabora, Ltd.
+# SPDX-License-Identifier: BSL-1.0
+
+set(XRT_INSTALL_ABSOLUTE_SYSTEMD_UNIT_FILES @XRT_INSTALL_ABSOLUTE_SYSTEMD_UNIT_FILES@)
+
+# Set up variables to use when configuring files
+set(conflicts @conflicts@)
+set(exit_on_disconnect @exit_on_disconnect@)
+set(service_path "monado-service")
+if(XRT_INSTALL_ABSOLUTE_SYSTEMD_UNIT_FILES)
+    set(service_path
+        "${CMAKE_INSTALL_PREFIX}/@CMAKE_INSTALL_BINDIR@/${service_path}")
+endif()
+
+# Create unit files
+configure_file(@SOCKET_INPUT@ "@CMAKE_CURRENT_BINARY_DIR@/@UNIT_NAME@.socket")
+configure_file(@SERVICE_INPUT@ "@CMAKE_CURRENT_BINARY_DIR@/@UNIT_NAME@.service")
+
+# Install them
+file(
+    INSTALL
+    DESTINATION "@UNIT_DIR@"
+    TYPE FILE
+    FILES
+    "@CMAKE_CURRENT_BINARY_DIR@/@UNIT_NAME@.socket"
+    "@CMAKE_CURRENT_BINARY_DIR@/@UNIT_NAME@.service")
diff --git a/src/xrt/targets/service/monado.in.service b/src/xrt/targets/service/monado.in.service
new file mode 100644
index 0000000000000000000000000000000000000000..b32a79bf69743ba112f74c3d1de185aede8279d3
--- /dev/null
+++ b/src/xrt/targets/service/monado.in.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Monado XR runtime service module @extra_desc@
+Requires=%N.socket
+ConditionUser=!root
+Conflicts=@conflicts@.service
+
+[Service]
+ExecStart=@service_path@
+Environment="XRT_COMPOSITOR_PRINT_DEBUG=on" "XRT_PRINT_OPTIONS=on" "IPC_EXIT_ON_DISCONNECT=@exit_on_disconnect@"
+# MemoryDenyWriteExecute=yes
+# NoNewPrivileges=yes
+Restart=no
+
+[Install]
+Also=%N.socket
diff --git a/src/xrt/targets/service/monado.in.socket b/src/xrt/targets/service/monado.in.socket
new file mode 100644
index 0000000000000000000000000000000000000000..7192215cc71f9bc36e52732aa02975803751c6e9
--- /dev/null
+++ b/src/xrt/targets/service/monado.in.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=Monado XR service module connection socket @extra_desc@
+ConditionUser=!root
+Conflicts=@conflicts@.socket
+
+[Socket]
+ListenStream=/tmp/monado_comp_ipc
+
+[Install]
+WantedBy=sockets.target