diff --git a/meson.build b/meson.build
index ce112f7743bd81a0454a7b193ae34449f98ad6ae..653e15f4b0281cad3dfd339039a55346b8fd7741 100644
--- a/meson.build
+++ b/meson.build
@@ -13,6 +13,12 @@ wireplumber_so_version = '0'
 
 wireplumber_headers_dir = join_paths(get_option('includedir'), 'wireplumber-' + wireplumber_api_version, 'wp')
 
+if get_option('bindir').startswith('/')
+  wireplumber_bin_dir = get_option('bindir')
+else
+  wireplumber_bin_dir = join_paths(get_option('prefix'), get_option('bindir'))
+endif
+
 if get_option('libdir').startswith('/')
   wireplumber_module_dir = join_paths(get_option('libdir'), 'wireplumber-' + wireplumber_api_version)
 else
diff --git a/meson_options.txt b/meson_options.txt
index 2ce003fdfc5c354c244cc4f7d80c60ba3bd4c40f..20d82b08a0243a2f763bdede461cbb238fd52797 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,3 +4,18 @@ option('doc', type : 'feature', value : 'auto',
        description: 'Enable documentation.')
 option('system-lua', type : 'boolean', value : 'false',
        description : 'Use lua from the system instead of the bundled one')
+option('systemd',
+       type: 'feature', value: 'auto',
+       description: 'Enable installing systemd units')
+option('systemd-system-service',
+       type : 'boolean', value : false,
+       description: 'Install systemd system service file')
+option('systemd-user-service',
+       type : 'boolean', value : true,
+       description: 'Install systemd user service file')
+option('systemd-system-unit-dir',
+       type : 'string',
+       description : 'Directory for system systemd units')
+option('systemd-user-unit-dir',
+       type : 'string',
+       description : 'Directory for user systemd units')
diff --git a/src/meson.build b/src/meson.build
index 6060834e6086cd45f875bad191b931327e78d457..50013d68105126778666b37d407fff3af8837279 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -11,6 +11,8 @@ install_subdir('scripts',
   strip_directory : false
 )
 
+subdir('systemd')
+
 executable('wireplumber',
   wp_sources,
   c_args : [
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..b02158f7e47fc504559fd3279fb785db351a991f
--- /dev/null
+++ b/src/systemd/meson.build
@@ -0,0 +1,32 @@
+systemd = dependency('systemd', required: get_option('systemd'))
+
+if systemd.found()
+  systemd_config = configuration_data()
+  systemd_config.set('WP_BINARY', join_paths(wireplumber_bin_dir, 'wireplumber'))
+
+  # system service
+  if get_option('systemd-system-service')
+    systemd_system_unit_dir = systemd.get_pkgconfig_variable(
+        'systemd_system_unit_dir',
+        define_variable : ['prefix', get_option('prefix')])
+
+    if get_option('systemd-system-unit-dir') != ''
+      systemd_system_unit_dir = get_option('systemd-system-unit-dir')
+    endif
+
+    subdir('system')
+  endif
+
+  # user service
+  if get_option('systemd-user-service')
+    systemd_user_unit_dir = systemd.get_pkgconfig_variable(
+        'systemd_user_unit_dir',
+        define_variable : ['prefix', get_option('prefix')])
+
+    if get_option('systemd-user-unit-dir') != ''
+      systemd_user_unit_dir = get_option('systemd-user-unit-dir')
+    endif
+
+    subdir('user')
+  endif
+endif
diff --git a/src/systemd/system/meson.build b/src/systemd/system/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..2a2a113deb7419a3fa29f43790d2334615c47717
--- /dev/null
+++ b/src/systemd/system/meson.build
@@ -0,0 +1,4 @@
+configure_file(input : 'wireplumber.service.in',
+               output : 'wireplumber.service',
+               configuration : systemd_config,
+               install_dir : systemd_system_unit_dir)
diff --git a/src/systemd/system/wireplumber.service.in b/src/systemd/system/wireplumber.service.in
new file mode 100644
index 0000000000000000000000000000000000000000..cf74b91ab289fd12d075c4603478e28188a9e423
--- /dev/null
+++ b/src/systemd/system/wireplumber.service.in
@@ -0,0 +1,21 @@
+[Unit]
+Description=Multimedia Service Session Manager
+After=pipewire.service
+BindsTo=pipewire.service
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+ExecStart=@WP_BINARY@
+Restart=on-failure
+RuntimeDirectory=pipewire
+User=pipewire
+Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire
+
+[Install]
+WantedBy=pipewire.service
diff --git a/src/systemd/user/meson.build b/src/systemd/user/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..831d58cf62a85baeae6959499949f00effb5e0ed
--- /dev/null
+++ b/src/systemd/user/meson.build
@@ -0,0 +1,4 @@
+configure_file(input : 'wireplumber.service.in',
+               output : 'wireplumber.service',
+               configuration : systemd_config,
+               install_dir : systemd_user_unit_dir)
diff --git a/src/systemd/user/wireplumber.service.in b/src/systemd/user/wireplumber.service.in
new file mode 100644
index 0000000000000000000000000000000000000000..35dcf81e6b6fc3de54f8f924ce39b1ca85129dc9
--- /dev/null
+++ b/src/systemd/user/wireplumber.service.in
@@ -0,0 +1,19 @@
+[Unit]
+Description=Multimedia Service Session Manager
+After=pipewire.service
+BindsTo=pipewire.service
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+ExecStart=@WP_BINARY@
+Restart=on-failure
+Slice=session.slice
+
+[Install]
+WantedBy=pipewire.service