How I Rescued My Unprivileged guix-daemon

After the recent round of Guix security vulnerability disclosures introduced a regression in the unprivileged guix-daemon, I was left with a Guix installation that refused to download substitutes.

Using Guix without substitutes is possible by just passing --no-substitutes to any Guix command, but this is a tradeoff with huge consequences: building my entire package tree from sources and minimal binary seeds would take days on my computer, during which I would not be able to use it for much else. Substitutes are a massive quality of life improvement for any use case where you are able to accept the modest (in most threat models) upstream supply chain risk.

Inspired by lunabee's guide for Guix System, I set out to get my Guix unstuck. It turned out to not be a complicated operation, so I'm documenting what I did in case it's helpful for anybody else.

Background

I run Guix on Vanilla OS inside a rootless Podman container, via apx. Vanilla is a Debian derivative, so it uses systemd and not shepherd, and has many other differences from Guix System, making guides focused on that distro less pertinent. I installed Guix using the binary installation script.

Running the daemon as root

First, I double checked to get the exact path to the Guix daemon executable and the flags used:

  sudo systemctl status guix-daemon
● guix-daemon.service - Build daemon for GNU Guix
     Loaded: loaded (/etc/systemd/system/guix-daemon.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-07-10 18:27:25 UTC; 21min ago
 Invocation: 5f43e0237e634071a0ec263ef4713147
    Process: 6131 ExecStartPre=systemctl stop gnu-store.mount (code=exited, status=0/SUCCESS)
    Process: 6134 ExecStartPost=systemctl start gnu-store.mount --no-block (code=exited, status=0/SUCCESS)
   Main PID: 6132 (guix-daemon)
      Tasks: 1 (limit: 8192)
     Memory: 1.8M (peak: 133.3M)
        CPU: 1.164s
     CGroup: /system.slice/guix-daemon.service
             └─6132 guix-daemon --discover=yes "--substitute-urls=https://bordeaux.guix.gnu.org https://ci.guix.gnu.org"

I can see that the unit file for my daemon service is present at /etc/systemd/system/guix-daemon.service, so let's peek inside there:

  # [snip]
  [Service]
  ExecStart=/var/guix/profiles/per-user/root/current-guix/bin/guix-daemon \
      --discover=yes \
      --substitute-urls='https://bordeaux.guix.gnu.org https://ci.guix.gnu.org'
  Environment='GUIX_STATE_DIRECTORY=/var/guix' 'GUIX_LOCPATH=/var/guix/profiles/per-user/root/guix-profile/lib/locale' LC_ALL=en_US.utf8
  # [snip]

This tells us what we need to know: the exact path to the guix-daemon binary, the exact flags used, and relevant environment variables.

So now that we've done our research, we stop the daemon and run it again as root (inside the container):

  sudo systemctl stop guix-daemon
  sudo -i [env] [guix-daemon] [args]

In my case, the latter invocation looks like:

  sudo -i \
       GUIX_STATE_DIRECTORY=/var/guix \
       GUIX_LOCPATH=/var/guix/profiles/per-user/root/guix-profile/lib/locale \
       LC_ALL=en_US.utf8 \
       /var/guix/profiles/per-user/root/current-guix/bin/guix-daemon \
       --discover=yes \
       --substitute-urls='https://bordeaux.guix.gnu.org https://ci.guix.gnu.org'

Pull and fix ownership

With our daemon running temporarily as root instead of its normal unprivileged guix-daemon user, it is able to overcome the regression and use substitutes, so we pull the latest Guix by running sudo -i guix pull in another shell within the same container.

After this succeeds, shut down the Guix daemon running as root (Ctrl+c is fine.) New objects in the Guix store are going to be owned by root instead of by guix-daemon which is going to mess us up once we try to return to business as normal. To fix this, we need to re-mount the normally read-only Guix store as read+write and then return ownership of all files to guix-daemon:

  sudo umount /gnu/store
  sudo mount -a
  sudo chmod -R guix-daemon:guix-daemon /gnu/store

Now you can log out and log back in, which should remount the store and restart the daemon.

Back to business as usual

After updating and restarting the Guix daemon, we should succeed at running guix pull and other commands as our regular user.

Hopefully we won't see a similar regression in the future, but if for some reason you desire to run your daemon temporarily as root in the future, I hope this is a handy reference.