Help: Shared local SteamLibrary, multi-user setup

Hi, I’m attempting to make a multi-user setup in order to separate our save games, browsing history, etc as much as possible.

Let’s say I have three users
-user1 (admin)
-user2 (user)
-user3 (user)

I created a group named steam
(sudo groupadd steam)
Then I added the users to the group
(sudo usermod -aG user1, etc)
Then to minimize disk usage for shared games, I created a directory for the common SteamLibrary
(sudo mkdir -p /var/games/SteamLibrary)
I then changed the owner group to steam
(sudo chgrp steam /var/games/SteamLibrary)
and also granted read-write-execute permission to the group
(sudo chmod 2775 /var/games/SteamLibrary)
To cover my base, I also added ACLs to grant write access to the steam group
(sudo setfacl -d -m g:steam:rwx /var/games/SteamLibrary)

I then setup the kids Steam account in their respective user profiles, and added the shared SteamLibrary in their Storage.

Steam sees the games, and all seems good.

However, when I try to start a game from the shared SteamLibrary, the games seems to fail silently.

Is there a guide on how to accomplish that kind of setup, or best practices to follow?

I’d really like to avoid having multi-GB games copies present in each user’s home directory, and keep our browser history, game saves, desktop environment setup as separate as possible.

Thanks!

Quick status update, so after fiddling a bit more and not finding a simple solution, I decided to have a single OS account for games (Steam, Heroic Launcher) and leveral the Steam switch user feature and have it autostart in Big Picture mode.

I use a non-gaming OS account for casual stuff to keep my browsing history, etc separate and locked away from others. That way my kids can login to that gaming session without me worrying about them messing up with the settings.

I have been fighting with this exact same thing. I’d like to have a shared Steam library for all users on my PC. The problem is Steam is moving the game’s Wine(Proton) prefix with the game files into the shared folder. The reason seems to be Steam seeing any added library as an external drive when an immutable file system is in use and hence moves the prefix with game files. Prefixes are per users and you can’t run a game with another user’s prefix. If you delete the prefix from shared library and let Steam re-create it for another user, then the game works for that other user, but no one else.

I haven’t actually gotten to try this on a distro with un-jailed filesystem so I could create a dedicated library folder somewhere else than /var, but probably will someday when I’m bored again. If it works it would definitely be a feature suggestion for Bazzite to have a dedicated, shared library folder somewhere.

I tried with CachyOS and open filesystem and exactly the same thing happened. Prefix wont stay in /home.

Omg I actually updated the docs with a PR about this yesterday, they seemed to think it wouldn’t be a common issue :stuck_out_tongue:

One of the things it could be is WINE

Basically you will just want to create an additional Steam library folder for the other users involved, and create a symlink for things like common (maybe shadercache too, and similarly copy or link any appmanifests.. just not temp or compatdata or workshop I think). Then add that new folder as the steam library for the additional user. The symlink of common will work and allow access to common game data, appmanifests make it show up in your steam list, and WINE gets to create new prefixes in a new compatdata folder for the new user.

I don’t know if this fix creates any additional quirks, but it’s been successful for getting games to launch on my other user account on my laptop

Thank you, I’ll have a look into it!

Thank you, I will try this.

I don’t see this as a WINE issue, but Steam being an idiot about handling prefixes. Other launchers have no problems with multiple users and prefixes. I will still try this, but don’t much like handling appmanifest files manually.

Disclaimer: The investigation, solution and this post was done in a collaboration with Claude.

We have a family gaming PC (Bazzite, but this applies to any Linux) with several local user accounts, each with their own Steam account. We wanted every game installed once, playable by everyone — without breaking Proton. Getting there took some debugging, so here’s the full working recipe including the pitfalls we hit.

The short version: game files can be shared through a common Steam library folder with group permissions + ACLs. Proton prefixes (compatdata) can NOT be shared — Wine refuses to use a prefix it doesn’t own — so we point compatdata through a symlink into the home directory of whoever is currently at the keyboard, switched automatically by a tiny systemd user service + timer.

The Shorter version:

Use a non official Steam Proton version. Apply Step 1, Step 2b, Step 2c. Reboot and play games.

Throughout this post the shared library is /var/home/steam-share (Bazzite/ostree note: /home is a symlink to /var/home; on regular distros just use /home/steam-share) and the group is steam-users. Adjust to taste.


Step 1 — Create the group and the shared library folder

sudo groupadd steam-users
sudo usermod -aG steam-users alice   # repeat for each user
# each user must log out and back in for the group to take effect!

sudo mkdir /var/home/steam-share
sudo chown root:steam-users /var/home/steam-share
sudo chmod 2770 /var/home/steam-share
sudo setfacl -m g:steam-users:rwX -d -m g:steam-users:rwX /var/home/steam-share

What this does:

  • chmod 2770 sets the setgid bit on the directory, so everything created inside inherits the steam-users group.
  • The setfacl line adds a default ACL: every file/dir any user creates in there is automatically group-readable/writable (X = execute only where it makes sense). This is what makes “user A installs, user B updates” work without anyone ever running chmod -R again.

Step 2 — Add it as a library in each user’s Steam

In every user’s Steam client: Settings → Storage → (dropdown) → Add Drive → pick /var/home/steam-share. Steam creates the steamapps structure on first use; the setgid bit + default ACL take care of permissions. Games installed there by one user show up as installed for everyone (each user still needs to own the game on their Steam account, or use Family Sharing).

:warning: Pitfall 1 — never chmod -R g+s the library

If you try to fix permissions with a recursive setgid (chmod -R 2770 or chmod -R g+s), the bit also lands on regular files, including the Steam Linux Runtime’s executables. Executing a setgid binary makes the kernel flag the process as “security elevated”, and Valve’s runtime then refuses to start. Every Proton game dies instantly with this in ~/.local/share/Steam/logs/console-linux.txt:

steam-runtime-tools-CRITICAL **: _srt_constructor: assertion '_srt_check_not_setuid ()' failed
pressure-vessel-wrap[1234]: Internal error: _srt_find_myself: assertion '_srt_check_not_setuid ()' failed

Setgid belongs on directories only. If you already made this mistake, this fixes it (keeps the bit on dirs):

sudo find /var/home/steam-share -type f -perm -2000 -exec chmod g-s {} +

:warning: Pitfall 2 — Proton prefixes cannot be shared

The first user to play a Proton game works fine. The second user gets an instant crash with this in the log:

wine: '/var/home/steam-share/steamapps/compatdata/1222680/pfx' is not owned by you

Wine hard-requires the prefix directory to be owned by the launching user — group permissions and ACLs cannot bypass an ownership check. So each user needs their own compatdata. Rather than setting STEAM_COMPAT_DATA_PATH=... %command% launch options on every game for every user, we made compatdata in the shared library a symlink into the active user’s home, switched automatically at login and on user switch.

2a. One-time migration (as root) - OPTIONAL - You can also just use a clean prefix directory.

Move any existing prefixes to their owners and create the initial link:

cd /var/home/steam-share/steamapps
for d in compatdata/*/; do
  owner=$(stat -c %U "$d")
  dest="/var/home/$owner/.local/share/Steam/steamapps/compatdata"
  sudo -u "$owner" mkdir -p "$dest"
  sudo mv "$d" "$dest/"
done
sudo rmdir compatdata
sudo ln -s "$HOME/.local/share/Steam/steamapps/compatdata" compatdata

2b. The switcher script

/usr/local/bin/steam-compatdata-switch (root-owned, chmod 755):

#!/usr/bin/env bash
# Point the shared library's compatdata at this user - but only if this
# user currently owns the active (foreground) session.
state=$(loginctl show-user "$(id -u)" --property=State --value 2>/dev/null)
[ "$state" = "active" ] || exit 0
target="$HOME/.local/share/Steam/steamapps/compatdata"
link=/var/home/steam-share/steamapps/compatdata
mkdir -p "$target"
[ "$(readlink "$link")" = "$target" ] || ln -sfn "$target" "$link"

The loginctl guard matters: without it, a user logging in briefly steals the link and it never flips back to the person still playing. With the guard + the timer below, the link always follows whoever is actually at the keyboard, within 30 seconds.

2c. Run it at login and every 30s — for all users, with two files

/etc/systemd/user/steam-compatdata-switch.service:

[Unit]
Description=Point shared Steam library compatdata at this user

[Service]
Type=oneshot
ExecStart=/usr/local/bin/steam-compatdata-switch

[Install]
WantedBy=default.target

/etc/systemd/user/steam-compatdata-switch.timer:

[Unit]
Description=Keep shared Steam compatdata pointing at the active user

[Timer]
OnStartupSec=15
OnUnitActiveSec=30
AccuracySec=5

[Install]
WantedBy=timers.target

Enable both globally (that’s the trick — it applies to every current and future user, no per-user setup):

sudo systemctl --global enable steam-compatdata-switch.service steam-compatdata-switch.timer

Because these are systemd user units they run in both desktop and Game Mode (gamescope) sessions. Users not in steam-users just fail to move the link, harmlessly.

:warning: OPTIONAL Pitfall 3 — shared Proton needs a permission fixup after every update.

Proton from Steam (“Proton - Experimental” etc.) also installs into a Steam library. If it lives in the shared one, there’s a subtle trap: after each Proton update, Proton’s launcher runs a one-time chmod pass over its own files (steampipe_fixups.json restore — Steam’s download system doesn’t preserve Unix permissions). chmod only works for the file owner, so the first person to launch after an update must be the user whose Steam downloaded it. Everyone else crashes with a Python traceback ending like:

PermissionError: [Errno 1] Operation not permitted:
'.../Proton - Experimental/files/lib/wine/dxvk/i386-windows/openvr_api_dxvk.dll'

Once the fixup has run once, it’s skipped for everybody (there’s a marker file), so day-to-day everything works. Your options, simplest first:

  1. Live with it: when it happens, the user who owns the Proton files launches any Proton game once — fixed for everyone.
  2. Keep Proton per-user: let each user keep Proton/compat tools in their home library (or use GE-Proton in ~/.local/share/Steam/compatibilitytools.d/, which is per-user by nature) and share only the games. Costs a couple of GB per user, avoids the problem entirely.
  3. Automate with a root helper that watches steampipe_fixups.json and applies the fixup as root, so the first launch after an update works for any user. Full implementation below.

3a. The fixup helper

One security warning first: do not execute Proton’s bundled Python as root — it sits in a group-writable directory, so anyone in steam-users could swap it out and get root. The helper below reimplements the fixup in a root-owned file, treats the manifest as pure data, and refuses paths that escape the Proton directory.

/usr/local/bin/steam-proton-fixups (root-owned, chmod 755):

#!/usr/bin/env python3
"""Apply Proton's steampipe permission fixups as root for shared installs."""
import json, os, stat, sys

COMMON = "/var/home/steam-share/steamapps/common"

def fixup(tooldir):
    manifest = os.path.join(tooldir, "steampipe_fixups.json")
    marker = os.path.join(tooldir, "files", "steampipe_fixups_mtime")
    if not os.path.isfile(manifest):
        return
    want = str(os.path.getmtime(manifest))
    try:
        with open(marker) as f:
            if f.readline().strip() == want:
                return          # already done for this Proton build
    except OSError:
        pass
    with open(manifest) as f:
        loaded = json.load(f)
    root = os.path.realpath(tooldir)
    for entry in loaded.get("empty_dirs", []):
        p = os.path.join(tooldir, entry)
        if not os.path.realpath(p).startswith(root + os.sep):
            print(f"skipping suspicious path {entry!r}", file=sys.stderr)
            continue
        os.makedirs(p, exist_ok=True)
    for entry in loaded.get("no_write_paths", []):
        p = os.path.join(tooldir, entry)
        if not os.path.realpath(p).startswith(root + os.sep):
            print(f"skipping suspicious path {entry!r}", file=sys.stderr)
            continue
        try:
            st = os.lstat(p)
        except FileNotFoundError:
            continue
        if not stat.S_ISLNK(st.st_mode):
            os.chmod(p, st.st_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
    os.makedirs(os.path.dirname(marker), exist_ok=True)
    with open(marker, "w") as f:
        f.write(want + "\n")
    print(f"applied fixups for {tooldir}")

if os.path.isdir(COMMON):
    for name in sorted(os.listdir(COMMON)):
        fixup(os.path.join(COMMON, name))

It mirrors exactly what Proton’s do_steampipe_fixups() does — recreate empty_dirs, strip write bits from no_write_paths, then write the marker Proton compares against (str(os.path.getmtime(...)) of the manifest) — but as root, so ownership doesn’t matter. It’s idempotent and skips tools whose marker is already current, so it’s safe to run any time.

3b. Trigger it automatically

/etc/systemd/system/steam-proton-fixups.service:

[Unit]
Description=Apply Proton steampipe fixups on the shared Steam library

[Service]
Type=oneshot
ExecStart=/usr/local/bin/steam-proton-fixups

/etc/systemd/system/steam-proton-fixups.path — fires the service the moment a Proton update lands (add a PathChanged= line per shared Proton version you keep):

[Unit]
Description=Watch shared Proton installs for updates

[Path]
PathChanged=/var/home/steam-share/steamapps/common/Proton - Experimental/steampipe_fixups.json
PathModified=/var/home/steam-share/steamapps/common

[Install]
WantedBy=multi-user.target

/etc/systemd/system/steam-proton-fixups.timer — daily catch-all in case the path watch misses something:

[Unit]
Description=Daily catch-all for Proton steampipe fixups

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Enable everything and run it once immediately:

sudo systemctl daemon-reload
sudo systemctl enable --now steam-proton-fixups.path steam-proton-fixups.timer
sudo /usr/local/bin/steam-proton-fixups

Notes & limitations

  • EA / Ubisoft games: launchers like the EA App install inside the prefix, so each user gets their own copy and logs in once on first launch. That’s per-user by design and actually what you want. (Tip: EA App’s first-run VC++ redistributable install can hang under Wine on the old-version uninstall step — if “Launching…” sits there for 5+ minutes with the VC_redist.x86.exe -uninstall process at 0% CPU, killing that process lets setup continue.)
  • One player at a time: the symlink has a single target, so games launched by a backgrounded user while someone else is active will fail until they’re active again. Running games mostly survive a flip, but don’t rely on it. If you genuinely need simultaneous gaming sessions, look at pam_namespace polyinstantiation (per-session bind mounts over compatdata) instead of a symlink.
  • Saves: most saves live in the prefix (= per user, good) or in Steam Cloud. Nothing to do.
  • Shader caches (steamapps/shadercache) share fine — no ownership checks there, and sharing saves compile time.
  • Where to look when something breaks: ~/.local/share/Steam/logs/console-linux.txt (launch errors, the errors quoted above all show up here), gameprocess_log.txt (did the process die instantly?), compat_log.txt (which Proton was picked).

Happy shared gaming! Corrections welcome.