Part 1 - too long to fit in one post.
Samba relies on a privilege escalation tool (become_root) to atomically modify file metadata. If you attempt a naive bind-mount deployment, the host kernel intercepts these root calls as an unprivileged user (100000), blocking modifications to the file metadata and throwing an Operation not permitted (EPERM) error.
To bypass this safely, we use a segmented User Namespace layout:
Standalone Server Authentication: If your network users mirror your local Unix accounts 1:1, Samba skips global identity allocation databases (idmap config). You can safely omit global ID mapping ranges if access is restricted to this dedicated storage user.
Permissions Context (chmod 775): Because we explicitly sever the POSIX permission sync with acl_xattr:ignore system acls = yes, Windows handles inheritance entirely in user space [3]. Reverting to standard Unix group inheritance configurations like chmod 2770 is redundant and can misdirect Windows metadata sync workers. A baseline of 775 provides the ideal uninhibited path for user-space metadata.
Managing all of that can become painful when one has a number of shares.
One of the nicest ways to manage this is to configure the ZFS datasets in a nested folder tree. This guide assumes you’ll follow the same pattern.
Here’s a representation of my own set:
With this setup, it’s also easy to add more shares at any time without rebooting the LXC.
The traditional method requires host set up for every dataset/share:
With this architecture, the placeholder host dataset is {smbpool} == mypool/smb
(continued...)
A Definitive Guide: High-Performance Samba + Native ZFS Windows ACLs in an Unprivileged Proxmox LXC
This blueprint configures:- a standalone Samba file server running
- inside an unprivileged Proxmox LXC container while
- storing Windows NT ACLs directly inside high-performance OpenZFS inodes (xattr=sa).
Variable Placeholders Used in This Guide (with examples from my setup)
- {smbpool}: The name of your ZFS storage pool on the host system, including the top dataset. (e.g. bfpool/smb)
- {CTID}: The numeric ID of your Proxmox LXC container (e.g., 101).
- {slxc}: The hostname of your Linux container. (eg "fern")
- {smbmnt}: The base mount directory path inside the LXC container (e.g., /bfp).
- {share1}, {share2}: names of individual ZFS datasets / SMB network shares. (see sample below)
- {mywkgrp}: Your Windows network Workgroup name. (default is WORKGROUP)
- {szuser} / {szgrp}: The storage user/group identity that owns the files. (you can have more than one
) - {szuid} / {szgid}: The explicit host-side integer numeric UID / GID assigned to the storage user/group.
0. Crucial Pre-Setup Architectural Decisions
Choose UID/GID's to own your datasets
In an unprivileged Proxmox LXC container, UID 0 (root) inside the container is shifted by default to UID 100000 on the host system.Samba relies on a privilege escalation tool (become_root) to atomically modify file metadata. If you attempt a naive bind-mount deployment, the host kernel intercepts these root calls as an unprivileged user (100000), blocking modifications to the file metadata and throwing an Operation not permitted (EPERM) error.
To bypass this safely, we use a segmented User Namespace layout:
- System service and other uid's: All UID's/GID's from 0 (root) to the end -- other than those that will own your datasets, remain secure by being mapped to the host's unprivileged subordinate block (100000+). This maintains container isolation. (Note: mapping root 1:1 to the host causes this to become a privileged container. Avoiding that is a key attribute of this Guide!
- Data Storage Space: Your user(s) that owns the dataset(s) ({szuid}) maps 1:1 directly to the host system. (You can have more than one of these.)
ZFS System Attribute & Authentication Reality
The ZFS Bypass: Because OpenZFS maps extended attributes using System Attributes (xattr=sa), metadata updates write straight into internal dataset inodes. This entirely bypasses the standard Linux VFS directory engine, allowing an unprivileged container to process user.NTACL streams without requiring zfs xattr capabilities. (ntfsv4 zfs support is coming, but not available yet other than on freebsd etc.)Standalone Server Authentication: If your network users mirror your local Unix accounts 1:1, Samba skips global identity allocation databases (idmap config). You can safely omit global ID mapping ranges if access is restricted to this dedicated storage user.
Permissions Context (chmod 775): Because we explicitly sever the POSIX permission sync with acl_xattr:ignore system acls = yes, Windows handles inheritance entirely in user space [3]. Reverting to standard Unix group inheritance configurations like chmod 2770 is redundant and can misdirect Windows metadata sync workers. A baseline of 775 provides the ideal uninhibited path for user-space metadata.
ZFS DataSet Simplified Architecture
A variety of settings apply to all, and/or various groups, of desired SMB shares.Managing all of that can become painful when one has a number of shares.
One of the nicest ways to manage this is to configure the ZFS datasets in a nested folder tree. This guide assumes you’ll follow the same pattern.
Here’s a representation of my own set:
Pool: mypool
Top level dataset: smb (not a share!)
Intermediate datasets: back, misc (not shares)
Leaf datasets:
/mypool/smb/back/bk1
/mypool/smb/back/bk2
/mypool/smb/misc/grpfiles
/mypool/smb/misc/project1
/mypool/smb/misc/project2
/mypool/smb/misc/temp
All of the above are recursively bound to the LXC using a single config line:lxc.mount.entry: /mypool/smb {smbmnt} none rbind,create=dir,rw 0 0With this setup, it’s also easy to add more shares at any time without rebooting the LXC.
The traditional method requires host set up for every dataset/share:
mp0: /{smbpool}, mp={smbmnt}With this architecture, the placeholder host dataset is {smbpool} == mypool/smb
Assumptions before what follows
This guide assumes you have already set up the following:- Created a ZFS pool, and subsidiary datasets to match your desired SMB shares. you may have pre-populated it with files.
- The whole tree is chown to {szuser}:{szgrp} with Uid:Gid {szuid}:{szgid}b) (or some small known set of UID/GID.)
- You have created a simple Proxmox unprivileged LXC, running debian 12 (for now), with backports available, with the ZFS pool rbind mapped. to properly configure the needed ZFS datasets, LXC *.conf .
- LXC id is {CTID}, mount point is {smbmnt}. LXC name is {slxc}
- You have installed but not fully configured SAMBA, latest backport version. Standalone server. Workgroup {mywkgrp}. Desired shares {share1} {share2}
- The LXC is stopped at the outset (we have some host work to do!)
- You have acl and xattr commands available (getfacl, getfattr, etc)
1. Pre-Flight Host Audit & Dataset Prep (Host CLI)
Before assigning storage pools or booting your container environment, you must audit the ZFS dataset tree to ensure no corrupt, malformed, or legacy permissions exist. Run these recursive diagnostic and cleaning commands on your Proxmox Host Terminal:# 1. Audit: Scan the entire tree for extended attributes (these settings search for any/all such attributes)getfattr -R -d -m '-' /{smbpool}# 2. Audit: Check for existing POSIX ACL enforcement flags in the disk tree, beyond standard rwx permissions:getfacl -cstR /{smbpool}# 3. Clean xattr: If anything showed up in #1, recursively strip all user.NTACL and legacy blocks from the entire datasetfind /{smbpool} -type f -exec setfattr -x user.NTACL {} + 2>/dev/nullfind /{smbpool} -type d -exec setfattr -x user.NTACL {} + 2>/dev/null# 4. Clean ACLs: Strip legacy POSIX ACL structures down to Unix mode bitssetfacl -R -b /{smbpool}# 5. Optimization: Force OpenZFS to record extended attributes directly inside file system inodeszfs set xattr=sa {smbpool}# 6. Optimization: Disable file access time writes to eliminate high-volume metadata amplification stallszfs set atime=off {smbpool}# 7. Optimization: Expose internal snapshot trees so the shadow_copy2 module can locate timelineszfs set snapdir=visible {smbpool}2. Proxmox Host Mapping (/etc/pve/lxc/{CTID}.conf)
Open the container configuration file on the host system and map your explicit user and group numerical space using non-overlapping mathematical zones:# Recursively bind-mount the parent ZFS dataset to the top level LXC folderlxc.mount.entry: /mypool/smb {smbmnt} none rbind,create=dir,rw 0 0# UID SEGMENTED MAPPING BOUNDSlxc.idmap: u 0 100000 {szuid}lxc.idmap: u {szuid} {szuid} 1lxc.idmap: u {szuid+1} {100001+szuid} {65535-szuid}# GID SEGMENTED MAPPING BOUNDSlxc.idmap: g 0 100000 {szgid}lxc.idmap: g {szgid} {szgid} 1lxc.idmap: g {szgid+1} {100001+szgid} {65535-szgid}3. Authorize Host Namespace Execution
The host system must explicitly trust the container to execute mappings inside these boundaries. Open /etc/subuid and /etc/subgid on the host terminal and append your exact identities and calculated remaining offsets:# Append to /etc/subuidroot:{szuid}:1root:{szuid+1}:{65535-szuid}# Append to /etc/subgidroot:{szgid}:1root:{szgid+1}:{65535-szgid}4. LXC Environment Pre-Configuration (LXC CLI)
Start your container {slxc}, login, and execute the system account and authentication database initialization steps:# 1. Create the matching system group using your exact host GIDgroupadd -g {szgid} {szgrp}# 2. Create the system user with shell execution disabled for network-only storage safetyuseradd -u {szuid} -g {szgid} -s /usr/sbin/nologin -M {szuser}# 3. Synchronize permissions on the root mount point container pathchown -R {szuser}:{szgrp} {smbmnt}chmod 775 {smbmnt}# 4. Initialize and register the account into the internal Samba SAM authentication databasesmbpasswd -a {szuser}(continued...)