summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2026-02-03 03:37:21 +0700
committerShulhan <ms@kilabit.info>2026-02-03 04:03:39 +0700
commit872bcf3f09ce30cc710899108b9326b08f4f2ea6 (patch)
treee64ae919c4fb6f3966b6d4914c16427af34d55a3
parent98facb83c7be5215ef13146ca56dea9639dde701 (diff)
downloadkilabit.info-872bcf3f09ce30cc710899108b9326b08f4f2ea6.tar.xz
journal/2026: new journal "Reducing Idle Services with systemd"
In this journal, we will take a look on how to minimize idle services running in the background using systemd-socket-proxyd(8) for services that we cannot control their code and using systemd.socket(5) for services that we can changes their code. In the _project submodules, we update awwan, gorankusu, pakakeh.go/cmd/httpdfs to run with socket based activation. This site program also has been modified to run with socket based activation too.
-rw-r--r--_content/index.adoc3
-rw-r--r--_content/journal/2026/index.adoc11
-rw-r--r--_content/journal/2026/reducing_idle_services/host_processes.pngbin0 -> 201651 bytes
-rw-r--r--_content/journal/2026/reducing_idle_services/index.adoc277
m---------_project/src/awwan0
m---------_project/src/gorankusu0
m---------_project/src/jarink0
m---------_project/src/lilin0
m---------_project/src/pakakeh.go0
m---------_project/src/rescached0
-rw-r--r--cmd/www-kilabit/main.go16
11 files changed, 305 insertions, 2 deletions
diff --git a/_content/index.adoc b/_content/index.adoc
index 98e0d6a..e1e593c 100644
--- a/_content/index.adoc
+++ b/_content/index.adoc
@@ -3,8 +3,7 @@
= kilabit.info
:description: Personal website of Muhammad Shulhan, contains list of open source projects, articles on software engineering, talks, and journals.
-:revnumber: v20260202-1
-:revdate: 2 February 2026
+:revnumber: v20260203-1
:sectanchors:
:stylesheet: default, index.css
:toc:
diff --git a/_content/journal/2026/index.adoc b/_content/journal/2026/index.adoc
index ff5d0fd..531ac52 100644
--- a/_content/journal/2026/index.adoc
+++ b/_content/journal/2026/index.adoc
@@ -3,6 +3,17 @@
=== 2026
+link:/journal/2026/reducing_idle_services/[Reducing Idle Services with
+SystemD^].
+In this journal, we will take a look on how to minimize idle services
+running in the background using
+https://man.archlinux.org/man/systemd-socket-proxyd.8[systemd-socket-proxyd(8)^]
+for services that we cannot control their code
+and using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^]
+for services that we can changes their code.
+
+
link:/journal/2026/systemd_socket_with_go/[systemd Socket Activation with Go^].
In this journal, we will take a look on how to implement socket-based
activation on Go program that listen and serve HTTP.
diff --git a/_content/journal/2026/reducing_idle_services/host_processes.png b/_content/journal/2026/reducing_idle_services/host_processes.png
new file mode 100644
index 0000000..6fcde60
--- /dev/null
+++ b/_content/journal/2026/reducing_idle_services/host_processes.png
Binary files differ
diff --git a/_content/journal/2026/reducing_idle_services/index.adoc b/_content/journal/2026/reducing_idle_services/index.adoc
new file mode 100644
index 0000000..bbf4775
--- /dev/null
+++ b/_content/journal/2026/reducing_idle_services/index.adoc
@@ -0,0 +1,277 @@
+= Reducing Idle Services with systemd
+1 February 2026
+:description: Minimize running services in Linux with systemd-socket-proxyd
+:sectanchors:
+:toc:
+
+In this journal, we will take a look on how to minimize idle services
+running in the background using
+https://man.archlinux.org/man/systemd-socket-proxyd.8[systemd-socket-proxyd(8)^]
+for services that we cannot control their code
+and using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^]
+for services that we can changes their code.
+
+== Background
+
+Lets take a look running processes on my host machine
+(click on the image to enlarge):
+
+.Host processes with top sorted by VIRT column
+image:host_processes.png[Host process,width=100%]
+
+At the top, we have a process with 261.3 gigabytes virtual memory address.
+This service rarely used, only once a day or less.
+
+Below that, we have HTTP services that I manage for local development using
+systemd user services.
+For example, this website has their own local domain `kilabit.local` that
+run on specific port.
+
+My goal is to reduce the running processes and activate it only when I
+opened the port or domain in the browser or when some other services connect
+to the port directly.
+
+As for measurement, we count number of tasks using `ps`, memory using
+`free`, and boot time using `systemd-analyze` after booting and login.
+Here is the initial value,
+
+----
+$ ps -e | wc -l
+198
+
+$ free -m
+ total used free shared buff/cache available
+Mem: 15620 3562 10370 142 2132 12058
+Swap: 4095 0 4095
+
+$ systemd-analyze
+Startup finished in 6.868s (firmware) + 2.737s (loader) + 934ms (kernel) + \
+ 1.693s (initrd) + 6.198s (userspace) = 18.432s
+graphical.target reached after 6.193s in userspace.
+
+$
+----
+
+Lets start with external services first.
+
+== Using systemd-socket-proxyd
+
+For external services where I cannot changes their code, I use
+systemd-socket-proxyd.
+
+[quote, systemd-socket-proxyd, https://man.archlinux.org/man/systemd-socket-proxyd.8]
+systemd-socket-proxyd is a generic socket-activated network socket
+forwarder proxy daemon for IPv4, IPv6 and UNIX stream sockets.
+It may be used to bi-directionally forward traffic from a local listening
+socket to a local or remote destination socket.
+
+For example, let say I have service `X` that listen on port 8080.
+We will let the systemd.socket listen to that port and run the actual
+service on port 18080.
+Any other services still connect to port 8080 to communicate to service `X`
+through systemd-socket-proxyd.
+
+First, we create the `/etc/systemd/system/proxy-X.socket` that will listen
+on port 8080,
+
+----
+[Socket]
+ListenStream=127.0.0.1:8080
+
+[Install]
+WantedBy=sockets.target
+----
+
+Next, we create the `/etc/systemd/system/proxy-X.service` that will run
+`systemd-socket-proxyd` and channel the traffic to port 18080,
+
+----
+[Unit]
+Requires=X.service
+Requires=proxy-X.socket
+After=X.service
+After=proxy-X.socket
+
+[Service]
+Type=notify
+ExecStartPre=/bin/sleep 3
+ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=5m \
+ 127.0.0.1:18080
+ExecStopPost=systemctl stop X.service
+----
+
+Note that, in this case, we add the `ExecStartPre=` to wait for `X.service`
+run successfully, and the `ExecStopPost=` to stop the service.
+Both of these may or may not needed, depends on the service.
+
+In the `X.service`, we override the service unit file to run on port 18080
+instead of default port,
+
+----
+# /etc/systemd/system/X.service.d/override.conf
+[Unit]
+StopWhenUnneeded=true
+
+[Service]
+ExecStart=
+ExecStart=/usr/bin/X --Port 18080
+----
+
+With `--exit-idle-time=5m` and `StopWhenUnneeded=true`, we tell the systemd
+to stop the service when no traffic received after 5 minutes.
+
+Now stop the actual service and start the `proxy-X.socket` service,
+
+----
+$ sudo systemctl disable --now X.service
+$ sudo systemctl enable --now proxy-X.socket
+$
+----
+
+We test it by opening the service port `:8080` in the browser.
+Here is sample log in the journal,
+
+----
+Feb 01 18:20:11 systemd[1]: Started X Daemon.
+Feb 01 18:20:11 systemd[1]: Starting proxy-X.service...
+<TRUNCATED>
+Feb 01 18:20:14 X[8144]: Now listening on: http://127.0.0.1:8080
+Feb 01 18:20:14 systemd[1]: Started proxy-X.service.
+----
+
+After 5 minutes we will the service stopped automatically,
+
+----
+Feb 01 18:25:34 systemd[1]: proxy-X.service: Deactivated successfully.
+Feb 01 18:25:34 systemd[1]: Stopping X Daemon...
+Feb 01 18:25:34 X[8144]: Application is shutting down...
+Feb 01 18:25:34 X[8144]: 02-01 18:25:34 Info X stopped
+Feb 01 18:25:34 systemd[1]: X.service: Deactivated successfully.
+Feb 01 18:25:34 systemd[1]: Stopped X Daemon.
+Feb 01 18:25:34 systemd[1]: X.service: Consumed 4.010s CPU time over 5min 23.145s wall clock time, 163.1M memory peak.
+----
+
+Great.
+We can repeat the same configurations to other services.
+
+
+== Using systemd.socket
+
+Now, the hard part is to rewrite all of the programs so it can be activated
+using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^].
+
+All of the program that we want to convert are built with Go.
+In the
+link:/journal/2026/systemd_socket_with_go/[previous journal],
+I have describe how to modify the Go program so it can be activate using
+systemd.socket(5).
+If you have a program that need to be converted too, but not written using
+Go, you can read the following blog first to understand the basic,
+
+- https://0pointer.de/blog/projects/socket-activation.html[systemd for Developers I^]
+- https://0pointer.de/blog/projects/socket-activation2.html[systemd for Developers II^]
+
+We will modify each of the main function in the program.
+There are at least 11 of them.
+Fortunately, most of them are using the same library/package.
+
+- local.awwan.service => link:/project/ciigo/[ciigo] based.
+ This serve the local version of https://awwan.org .
+
+- local.golang-id.service => link:/project/ciigo/[ciigo] based. This serve
+ the local version of https://golang-id.org .
+
+- local.golang-id-tour.service => net/http based.
+ This serve the local version of https://tour.golang-id.org .
+
+- local.home.bahasa-go.service => link:/project/ciigo/[ciigo] based.
+
+- local.home.gitdoc.service =>
+ https://git.sr.ht/~shulhan/pakakeh.go/tree/main/item/cmd/httpdfs[httpdfs]
+ => lib/http based. This serve the
+ https://git.kernel.org/pub/scm/git/git.git/tree/Documentation[Git documentation]
+ directory
+
+- local.home.go.service => net/http based.
+ This run golang.org/x/website for offline documentation (the
+ same content as https://go.dev ).
+ You can view the patch
+ https://git.sr.ht/~shulhan/go-x-website/commit/1490b977eefea3c9cb1b35aaf211cd6934299383[here^]
+ (subject to changes in the future).
+
+- local.home.godoc.service => net/http based.
+ This run golang.org/x/pkgsite with local modules.
+ You can see the patch
+ https://git.sr.ht/~shulhan/go-x-pkgsite/commit/a2088f34de993bb4a4bd24dd6d722605373e5d08[here^]
+ (subject to changes in the future).
+
+- local.home.gorankusu.service => lib/http based.
+
+- local.home.ops.service => lib/http based
+
+- local.kilabit.service => link:/project/ciigo/[ciigo] based.
+
+- local.kilabit.umum.service =>
+ https://git.sr.ht/~shulhan/pakakeh.go/tree/main/item/cmd/httpdfs[httpdfs]
+ => lib/http based.
+
+Most of the modification are adding the following identical code,
+
+----
+...
+ listeners, err := systemd.Listeners(true)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(listeners) > 1 {
+ log.Fatal(`too many listeners received for activation`)
+ }
+ var listener net.Listener
+ if len(listeners) == 1 {
+ listener = listeners[0]
+ gotAddr := listener.Addr().String()
+ if gotAddr != serveOpts.Address {
+ log.Fatalf(`invalid Listener address, got %s, want %s`,
+ gotAddr, serveOpts.Address)
+ }
+ }
+ // Pass the listener to HTTP Server.
+...
+----
+
+
+== Results
+
+Let's reboot the machine and see the results.
+
+----
+$ diff -u before after
+--- before 2026-02-02 23:52:11.677244743 +0700
++++ after 2026-02-02 23:52:25.315331792 +0700
+@@ -1,12 +1,12 @@
+ $ ps -e | wc -l
+-198
++182
+
+ $ free -m
+ total used free shared buff/cache available
+-Mem: 15620 3562 10370 142 2132 12058
++Mem: 15620 2898 12491 102 601 12722
+ Swap: 4095 0 4095
+
+ $ systemd-analyze
+-Startup finished in 6.868s (firmware) + 2.737s (loader) + 934ms (kernel) + \
+- 1.693s (initrd) + 6.198s (userspace) = 18.432s
+-graphical.target reached after 6.193s in userspace.
++Startup finished in 6.804s (firmware) + 1.438s (loader) + 985ms (kernel) + \
++ 1.508s (initrd) + 5.985s (userspace) = 16.722s
++graphical.target reached after 5.983s in userspace.
+----
+
+The number of processes started on boot reduced by 16,
+the total memory used reduced by 664 MB (-18%), and
+the boot time reduced by 1.71 second (-9%).
+Well, it is not so bad. The big win is in the reduced memory usage and
+learning how to use `systemd.socket` and `systemd-socket-proxyd`.
diff --git a/_project/src/awwan b/_project/src/awwan
-Subproject 663fdaf45162ac0eb1b45672f90fa58b73ab6f0
+Subproject bbbb67bc6cfff3a42db6b7f0feec6c6a54875be
diff --git a/_project/src/gorankusu b/_project/src/gorankusu
-Subproject 34cb54fc8fc68dededfe57e42ba8ae50d0d91fd
+Subproject a6947eef2c0efbd30fc2d93127c44537e4358ac
diff --git a/_project/src/jarink b/_project/src/jarink
-Subproject 2a4376d5ddeee82d4ef38f4953453abb43e8522
+Subproject a33efc3992f58355eb98d7a5574df955952924b
diff --git a/_project/src/lilin b/_project/src/lilin
-Subproject bf87c6ad4824c7ed1c990aeff2d2883936c1f20
+Subproject fbad5db9b998ca64feaffee78ba680300d3a52a
diff --git a/_project/src/pakakeh.go b/_project/src/pakakeh.go
-Subproject 1e3bb9be8444082dc1ada7f76a727b9a9f764d4
+Subproject ca3002fb7041a863600042f013e12faf73caf22
diff --git a/_project/src/rescached b/_project/src/rescached
-Subproject 3bbfcd5d598f0aa0f4d1f840f3ba7df4a74dbe5
+Subproject d30b17c4db1392c19b11c3af7a47051f8dd6568
diff --git a/cmd/www-kilabit/main.go b/cmd/www-kilabit/main.go
index d1bbaba..cfbcb41 100644
--- a/cmd/www-kilabit/main.go
+++ b/cmd/www-kilabit/main.go
@@ -10,6 +10,7 @@ import (
"git.sr.ht/~shulhan/ciigo"
"git.sr.ht/~shulhan/pakakeh.go/lib/memfs"
+ "git.sr.ht/~shulhan/pakakeh.go/lib/systemd"
)
const (
@@ -55,6 +56,21 @@ func main() {
}
default:
+ listeners, err := systemd.Listeners(true)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(listeners) > 1 {
+ log.Fatal(`too many listeners received for activation`)
+ }
+ if len(listeners) == 1 {
+ serveOpts.Listener = listeners[0]
+ gotAddr := serveOpts.Listener.Addr().String()
+ if gotAddr != serveOpts.Address {
+ log.Fatalf(`invalid Listener address, got %s, want %s`,
+ gotAddr, serveOpts.Address)
+ }
+ }
err = ciigo.Serve(serveOpts)
if err != nil {
log.Fatal(err)