Running urxvt as a daemon/client

The Wary Fox

linuxurxvt

1077 Words - Reading Time: 8 Minutes

2025/08/15 17:56


An unexpected finding

One evening, while looking for a flag I had forgotten in the urxvt man page, I noticed that it is possible to run urxvt as a daemon/client. The man page mentions it’s more memory efficient and has a faster start time:

It also comes with a client/daemon pair that lets you open any number of terminal windows from within a single process, which makes startup time very fast and drastically reduces memory usage. See urxvtd(1) (daemon) and urxvtc(1) (client).

urxvt man page [1]

Both urxvtd and urxvtc are provided by the rxvt-unicode package in Arch Linux:

pacman -Ql rxvt-unicode
rxvt-unicode /usr/
rxvt-unicode /usr/bin/
rxvt-unicode /usr/bin/urclock
rxvt-unicode /usr/bin/urxvt
rxvt-unicode /usr/bin/urxvt-tabbed
rxvt-unicode /usr/bin/urxvtc
rxvt-unicode /usr/bin/urxvtd
[...]

The daemon: urxvtd

urxvtd runs as a background daemon process and, new terminals can be created by using urxvtc instead of the usual urxvt.

There are several ways to do this, the simplest is to run urxvtd when you start your X11. Add this to your .xinitrc or your display manager of choice:

urxvtd --quiet --opendisplay --fork

The following option flags are used:

--quiet Normally, urxvtd outputs the message “rxvt-unicode daemon listening on path” after binding to its control socket. This option will suppress this message (errors and warnings will still be logged).

--opendisplay This forces urxvtd to open a connection to the current $DISPLAY and keep it open.

--fork This makes urxvtd fork after it has bound itself to its control socket.

The control socket will be in your home directory:

ls ~/.urxvt
urxvtd-arch

stat ~/.urxvt/urxvtd-arch
  File: urxvtd-arch
  Size: 0               Blocks: 0          IO Block: 4096   socket
Device: 259,7   Inode: 6291510     Links: 1
Access: (0700/srwx------)  Uid: ( 1000/   twfox)   Gid: ( 1000/   twfox)
Access: 2025-08-06 21:19:21.685587252 +0200
Modify: 2025-08-06 21:19:19.567587485 +0200
Change: 2025-08-06 21:19:19.567587485 +0200
 Birth: 2025-08-06 21:19:19.567587485 +0200

If you want to use Systemd instead, you can do so by creating these units. You have these in the urxvt: FAQ [2]

The urxvtd.service unit:

urxvtd.service
[Unit]
Description=urxvt terminal daemon

[Service]
ExecStart=/usr/bin/urxvtd -o

[Install]
RequiredBy=urxvtd.socket

And the urxvtd.socket unit:

urxvtd.socket
[Unit]
Description=urxvt terminal daemon socket

[Socket]
ListenStream=%h/.urxvt/urxvtd-%H

[Install]
WantedBy=sockets.target

You can create these inside /etc/systemd/user/ (since you will be running this for your user session), reload the config, and start/enable as any other Sytemd units:

systemctl --user daemon-reload

systemctl --user enable urxvtd.socket
systemctl --user start urxvtd.socket

Did we only start the urxvtd.socket? It is configured so urxvtd will start and listen to the socket as soon as it’s needed.

After starting urxvtd.socket it should look similar to this:

systemctl --user status urxvtd.socket
● urxvtd.service - urxvt terminal daemon
     Loaded: loaded (/etc/systemd/user/urxvtd.service; disabled; preset: enabled)
     Active: active (running) since Fri 2025-08-08 17:05:15 CEST; 841ms ago
 Invocation: 791a5c6158294551bd82635af8847060
TriggeredBy: ● urxvtd.socket
   Main PID: 1987452 (urxvtd)
      Tasks: 1 (limit: 76875)
     Memory: 840K (peak: 1.5M)
        CPU: 5ms
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/urxvtd.service
             └─1987452 /usr/bin/urxvtd -o

Aug 08 16:05:15 arch systemd[796]: Started urxvt terminal daemon.
Aug 08 16:05:15 arch urxvtd[1987452]: rxvt-unicode daemon listening on fd.

The client: urxvtc

Now, run the client urxvtc and check the service:

systemctl --user status urxvtd.service

● urxvtd.service - urxvt terminal daemon
     Loaded: loaded (/etc/systemd/user/urxvtd.service; disabled; preset: enabled)
     Active: active (running) since Fri 2025-08-08 17:11:30 CEST; 7s ago
 Invocation: 5278a3dad36a4c658b2c6e583733f157
TriggeredBy: ● urxvtd.socket
   Main PID: 2002293 (urxvtd)
      Tasks: 2 (limit: 76875)
     Memory: 16.1M (peak: 18.4M)
        CPU: 100ms
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/urxvtd.service
             ├─2002293 /usr/bin/urxvtd -o
             └─2002294 zsh

Aug 08 16:11:30 arch systemd[796]: Started urxvt terminal daemon.
Aug 08 16:11:30 arch urxvtd[2002293]: rxvt-unicode daemon listening on fd.

Notice urxvtd running with PID 2002293.

Check if urxvtd is listening to the socket correctly with lsof:

lsof -p $(pgrep urxvtd) | egrep 'LISTEN'
urxvtd  2129281 twfox   3u     unix 0x000000001fa80fe5      0t0   5039747 /home/twfox/.urxvt/urxvtd-arch type=STREAM (LISTEN)

If something isn’t working you can check the journal entries for the socket and the service.

Normal start/stop cycles should look like this:

journalctl -u urxvtd.socket
Aug 08 16:35:11 arch systemd[1]: Listening on urxvt terminal daemon socket.
Aug 08 16:37:55 arch systemd[1]: urxvtd.socket: Deactivated successfully.
Aug 08 16:37:55 arch systemd[1]: Closed urxvt terminal daemon socket.
journalctl -u urxvtd.service
Aug 08 16:35:11 arch systemd[1]: Started urxvt terminal daemon.
Aug 08 16:35:11 arch urxvtd[1949750]: rxvt-unicode daemon listening on fd.
Aug 08 16:37:49 arch systemd[1]: Stopping urxvt terminal daemon...
Aug 08 16:37:49 arch systemd[1]: urxvtd.service: Deactivated successfully.
Aug 08 16:37:49 arch systemd[1]: Stopped urxvt terminal daemon.

Comparison

Process hierarchy

If you open a few more instances of urxvtc, you will see a clear difference with the PID tree.

Here we have three terminals running pstree, ranger and iftop with urxvt.

pstree
├─zsh───urxvt───zsh───pstree
├─zsh───urxvt───zsh───ranger
└─zsh───urxvt───zsh───sudo───sudo───iftop───3*[{iftop}]

And here we have the three same terminals with urxvd and urxvtc:

├─urxvtd─┬─zsh───pstree
│        ├─zsh───ranger
│        └─zsh───sudo───sudo───iftop───3*[{iftop}]

The process hierarchy has changed, and now all the urxvt instances share the same urxvtd parent process.

Performance

Lets compare the memory footprint. In theory, urxvtd should help us with the memory footprint as it can more efficiently share memory between the different terminal children processes.

I start the same three urxvt instances running pstree, ranger and iftop.

ps -o pid,ppid,rss,vsz,pmem,comm -C urxvt
PID    PPID   RSS    VSZ      COMMAND
2054884 2054859 22076  50464  urxvt
2054952 2054941 22040  50456  urxvt
2054996 2054985 22216  50460  urxvt

And I do the same with urxvtd and urxvtc.

ps -p $(pgrep urxvtd) --ppid $(pgrep urxvtd) -o pid,ppid,rss,vsz,comm
PID     PPID    RSS   VSZ   COMMAND
2059496 796     23300 86732 urxvtd
2059497 2059496 8484  13148 zsh
2059566 2059496 8516  13144 zsh
2059652 2059496 8504  13144 zsh

urxvt (3 terminals):

  • Total RSS: 22,076 + 22,040 + 22,216 = 66,332 KB

urxvtd/urxvtc (3 terminals):

  • urxvtd: 23,300 KB
  • 3 urxvtc: 8,484 + 8,516 + 8,504 = 25,504 KB
  • Total RSS: 48,804 KB

That’s a 26% memory reduction (17.5 MB saved). I don’t know if its “drastic”, but there you go.

For the startup time benchmark, I will be using hyperfine [3].

hyperfine 'urxvt -e /bin/true' 'urxvtc -e /bin/true' --warmup 3
Benchmark 1: urxvt -e /bin/true
Time (mean ± σ):      38.5 ms ±   0.6 ms    [User: 31.9 ms, System: 6.5 ms]
Range (min … max):    37.6 ms …  41.4 ms    76 runs

Benchmark 2: urxvtc -e /bin/true
Time (mean ± σ):      12.9 ms ±   1.6 ms    [User: 0.4 ms, System: 0.6 ms]
Range (min … max):     9.9 ms …  18.2 ms    195 runs

That’s about 3x faster startup time!

Wrapping up

Not bad, huh? Remember that the only thing we are giving “away” is the fact that if the daemon crashes, all our terminals are gone.

The good news is, I have been using this for a few months and so far it hasn’t crashed a single time.