Feb 28 13:44:08 userproxy gssproxy[700]: [CID 16][2026/02/28 12:44:08]: gp_rpc_execute: executing 6 (GSSX_ACQUIRE_CRED) for service "nfs-client", euid: 0,socket: (null)
Feb 28 13:44:08 userproxy gssproxy[700]: [CID 16][2026/02/28 12:44:08]: gp_rpc_execute: executing 8 (GSSX_INIT_SEC_CONTEXT) for service "nfs-client", euid: 0,socket: (null)
Feb 28 13:44:08 userproxy gssproxy[700]: [CID 16][2026/02/28 12:44:08]: gp_rpc_execute: executing 6 (GSSX_ACQUIRE_CRED) for service "nfs-client", euid: 0,socket: (null)
Feb 28 13:44:08 userproxy gssproxy[700]: [CID 16][2026/02/28 12:44:08]: gp_rpc_execute: executing 8 (GSSX_INIT_SEC_CONTEXT) for service "nfs-client", euid: 0,socket: (null)
Feb 28 13:44:08 userproxy gssproxy[700]: [CID 15][2026/02/28 12:44:08]: gp_rpc_execute: executing 6 (GSSX_ACQUIRE_CRED) for service "nfs-client", euid: 1018,socket: (null)
Feb 28 13:44:08 userproxy gssproxy[700]: [CID 15][2026/02/28 12:44:08]: gp_rpc_execute: executing 8 (GSSX_INIT_SEC_CONTEXT) for service "nfs-client", euid: 1018,socket: (null)
cat webdav.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName userproxy.it-verband-chemnitz.de
ServerAlias webdav.it-verband-chemnitz.de
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/userproxy.it-verband-chemnitz.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/userproxy.it-verband-chemnitz.de/privkey.pem
# One attempt to make sure we never have the frontend touch the filesystem
# this means Location / goes to this Directory, will issue a stat()
DocumentRoot /var/www/empty
#userproxy front, if you connect here
<Directory />
# disallows .htaccess
AllowOverride None
# hmm well
Options None
# allow from all new syntax
Require all granted
</Directory>
# this makes Apache not look into the Directory /homes which would issue a lookup of .htaccess and stat() with user www-data
<Location /homes>
AuthType Basic
AuthName "WebDAV"
AuthBasicProvider PAM
AuthPAMService webdav
Require valid-user
# header passing authed user
RequestHeader set X-Remote-User expr=%{REMOTE_USER}
#SetHandler "proxy:http://127.0.0.1:8080/homes"
Options -FollowSymLinks
</Location>
# PROXYPRESERVEHOST
# without this the backend would not know who is connecting and just see 127.0.0.1:8080
# with it the backend gets the request parameter: Host: userproxy.it-verband-chemnitz.de.
# Gemini explain: Why you need it: WebDAV responses (like PROPFIND) contain XML tags called <D:href>.
## These tags tell the client where the files are. If the Backend thinks its name is 127.0.0.1,
## it will send links back to the client like http://127.0.0.1:8080/homes/richard/file.txt.
## The client (Nautilus) will try to click that and fail because it can't reach 127.0.0.1.
## ProxyPreserveHost ensures the Backend knows its "Public Name."
ProxyPreserveHost On
#RewriteEngine On
#RewriteCond %{REQUEST_URI} ^/home/[^/]+$
#RewriteRule ^(.*)$ $1/ [R=301,L]
# forwarder
## Apache's connection pooling.
## The Frontend ProxyPass keeps idle connections open to the Backend (127.0.0.1:8080) to save time. However, mpm_itk drops root privileges to richard on the first request. If the Frontend reuses that same connection milliseconds later for a background check without the auth header, or for another user, the backend child process is already richard. It cannot setuid() again because it is no longer root, causing the request to fail unexpectedly.
## Force the Frontend to close the connection after every single request so the Backend spawns a fresh mpm_itk worker from root every time.
ProxyPass /homes http://127.0.0.1:8080/safe-homes nocanon disablereuse=On
# this is NOT a backfeed, the data comes back via socket automagically. This only does:
## If the Backend sends a "Redirect" (like the trailing slash issue we had), it sends a header:
## Location: http://127.0.0.1:8080/homes/richard/
## If you didn't have ProxyPassReverse, the Frontend would send that exact string to Nautilus. Nautilus would try to connect to http://127.0.0.1:8080 and die.
## ProxyPassReverse sees that "Location" header, recognizes the 127.0.0.1:8080 part, and rewrites it to the Frontend's public URL:
## Location: https://userproxy.it-verband-chemnitz.de/homes/richard/
ProxyPassReverse /homes http://127.0.0.1:8080/homes
# im currently working on this RequestHeader edit
# =========================================================
# WEBDAV INTEROPERABILITY: THE DESTINATION HEADER
# =========================================================
# PROBLEM: WebDAV 'MOVE' and 'COPY' methods use a 'Destination' header
# to tell the server where to put the new file.
# The client (Nautilus/WinExplorer) sends an ABSOLUTE URL:
# Destination: https://userproxy.it-verband-chemnitz.de/homes/richard/new.txt
# CONFLICT: Our Backend (8080) is plain HTTP. When it sees an HTTPS
# destination, it thinks we are asking it to move a file to a
# DIFFERENT server (External Cross-Server Move), which it will deny (403/502).
# SOLUTION: We must "downgrade" the header to HTTP so the Backend
# recognizes the destination as its own local filesystem.
# The 'early' flag ensures this happens before the proxy logic executes.
RequestHeader edit Destination ^https:// http:// early
# NOTE: Since we are now using Namespace Matching (/homes -> /homes),
# we NO LONGER need to edit the path itself (e.g., /home -> /shadow_homes).
# This reduces complexity and improves reliability with GVfs.
</VirtualHost>
</IfModule>
<VirtualHost 127.0.0.1:8080>
DavLockDB /var/lib/apache2/webdav/DavLock
LogLevel alert rewrite:trace6 mpm_itk:trace3
## UseCanonicalName On: Prevents Apache from generating self-referential
## redirects using its internal IP (127.0.0.1). Forces it to use the ServerName.
ServerName https://userproxy.it-verband-chemnitz.de
UseCanonicalName On
#DocumentRoot /var/www/empty
Alias /safe-homes /var/www/empty
# dont go insane this is the http request /homes to the actual on disk folder /homes lol
# Alias /homes /homes
## IDENTITY BRIDGE: Capture the header from the Frontend.
## mpm_itk switches the process UID to Richard BEFORE the Directory walk.
# was in location which is wrong, drop to user priv asap
AssignUserIDExpr %{HTTP:X-Remote-User}
<Directory />
AllowOverride None
Options -FollowSymLinks -Indexes
Require all granted
</Directory>
# 3. The Pivot
<Location /safe-homes>
Require all granted
RewriteEngine On
# Because Alias ran first, the URI string here is "/var/www/empty/richard/"
# We capture the "/richard/" part and bounce it to "/homes/richard/"
RewriteRule ^/var/www/empty(.*)$ /homes$1 [L]
</Location>
Alias /homes /homes
#<LocationMatch "^/homes/([^/]+)">
#<Location /homes>
<Directory /homes>
DAV On
# DirectoryCheckHandler On: Critical for mpm_itk + Kerberos.
# It tells Apache to proceed even if the parent process (www-data)
# can't fully validate the path components.
# DirectoryCheckHandler On
# Disable .htaccess searches to prevent "EUID 33" probes on NFS.
AllowOverride None
# Options -FollowSymLinks: Prevents lstat() calls by the parent
# process. Using +SymLinksIfOwnerMatch is the "safe" compromise.
Options +Indexes +SymLinksIfOwnerMatch -MultiViews
Require all granted
</Directory>
#</Location>
#</LocationMatch>
# better mpm itk info
# LogLevel mpm_itk:info
# LogLevel mpm_itk:trace2
ErrorLog ${APACHE_LOG_DIR}/backend_error.log
CustomLog ${APACHE_LOG_DIR}/backend_access.log combined
# thanks gemini
## %P: Process ID
## %{tid}P: Thread ID (useful for event/worker MPMs)
## %u: Authenticated User (from header)
LuaHookLog /etc/apache2/get_uid.lua log_uid
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{X-Remote-User}i\" PID:%P OS_UID:%{system_uid}n" itk_debug
CustomLog ${APACHE_LOG_DIR}/backend_access.log itk_debug
</VirtualHost>
root@userproxy:/etc/apache2/sites-enabled# cat /etc/pam.d/webdav
#auth required pam_unix.so
#account required pam_unix.so
#session required pam_unix.so
# gets a user principal
# auth required pam_krb5.so minimum_uid=1000
# ccache_dir=/var/lib/gssproxy/webclients ccname_template=FILE:%d/krb5cc_%U
# This hook knows the password and stores the ticket to ccache_dir=/var/lib/gssproxy/webclients
auth required pam_exec.so expose_authtok /usr/local/bin/webdav-kinit.sh
# 2. Always succeed the "account" check
# This bypasses the need for the user to be in /etc/passwd
account required pam_permit.so
root@userproxy:/etc/apache2/sites-enabled# cat /usr/local/bin/webdav-kinit.sh
#!/bin/bash
# /usr/local/bin/webdav-kinit.sh
LOG_FILE="/var/log/webdav-kinit.log"
# 1. Capture the timestamp and metadata
# PAM_RHOST: The remote IP of the client (Nautilus/curl/etc)
# PAM_USER: The user logging in
# PAM_SERVICE: Usually 'webdav' (from your config)
NOW=$(date "+%Y-%m-%d %H:%M:%S")
CLIENT_IP="${PAM_RHOST:-unknown_ip}"
SERVICE="${PAM_SERVICE:-unknown_service}"
PID=$$
MOUNTPOINT="/homes/$PAM_USER"
# plaintext password from pam_exec
read -r PASSWORD
# Log the initial connection attempt
echo "[$NOW] [PID: $PID] AUTH_START: User='$PAM_USER' Client='$CLIENT_IP' Service='$SERVICE'" >> "$LOG_FILE"
USER_UID=$(id -u "$PAM_USER")
#CCACHE="/var/lib/gssproxy/webclients/krb5cc_${USER_UID}"
# better syntax similar to krb5 lines in pam
CCACHE_DIR="/var/lib/gssproxy/webclients"
CCACHE="$CCACHE_DIR/krb5cc_${USER_UID}"
# Request the ticket and force it into the gssproxy path
echo "$PASSWORD" | kinit -c "FILE:$CCACHE" "$PAM_USER"
KINIT_RET=$?
# exit 0
# kinit failed (bad password), exit immediately
if [ $KINIT_RET -ne 0 ]; then
echo "[$NOW] [PID: $PID] KINIT_FAIL: Code=$KINIT_RET Error='$KINIT_ERR'" >> "$LOG_FILE"
exit $KINIT_RET
fi
# 2. Trigger the AutoFS mount
# The Apache frontend runs as www-data, so doing this might result in "Permission denied"
# to read the folder contents. That does not matter! The simple act of calling stat()
# forces the kernel to ask AutoFS to resolve the path, which triggers the mount.
echo "[$NOW] [PID: $PID] KINIT_SUCCESS: Ticket saved to $CCACHE" >> "$LOG_FILE"
MOUNT_CHECK=$(grep -qs "/homes/$PAM_USER " /proc/mounts && echo "MOUNTED" || echo "NOT_MOUNTED_YET")
echo "[$NOW] [PID: $PID] MOUNT_STATUS_BEFORE: $MOUNT_CHECK" >> "$LOG_FILE"
echo "------------------------------------------------------------------" >> "$LOG_FILE"
if ! grep -qs "$MOUNTPOINT " /proc/mounts; then
# Not mounted? Poke it to wake up AutoFS.
# We suppress errors because www-data might get "Permission Denied"
# even if the mount succeeds (which is fine).
# stat "$MOUNTPOINT" >/dev/null 2>&1
# stat "$MOUNTPOINT" >> "$LOG_FILE"
mount $MOUNTPOINT
# POKE_OUT=$(/usr/bin/timeout 3s /usr/bin/ls -ld "$MOUNTPOINT" 2>&1)
# POKE_RET=$?
# if [ $POKE_RET -eq 0 ]; then
# echo "[$NOW] [PID: $PID] POKE_SUCCESS: $POKE_OUT" >> "$LOG_FILE"
# elif [ $POKE_RET -eq 124 ]; then
# echo "[$NOW] [PID: $PID] POKE_TIMEOUT: NFS Server did not respond in 3s!" >> "$LOG_FILE"
# else
# echo "[$NOW] [PID: $PID] POKE_MSG: $POKE_OUT (Code: $POKE_RET)" >> "$LOG_FILE"
# fi
fi
MOUNT_CHECK=$(grep -qs "/homes/$PAM_USER " /proc/mounts && echo "MOUNTED" || echo "NOT_MOUNTED_YET")
echo "[$NOW] [PID: $PID] MOUNT_STATUS_AFTER: $MOUNT_CHECK" >> "$LOG_FILE"
echo "------------------------------------------------------------------" >> "$LOG_FILE"
# timeout 0.5s stat "$MOUNTPOINT" >/dev/null 2>&1 || true
#if ! mountpoint -q "$MOUNTPOINT"; then
# Instead of stat, just try to 'ls' the PARENT directory
# to wake up AutoFS without touching the restricted files.
# ls /homes >/dev/null 2>&1
#fi
exit 0
1. If using Systemd Automounts
If your mounts are managed via systemd (which the logs suggest), you can find the global timeout in /etc/systemd/system.conf or by looking at the specific automount unit.
However, since these are likely dynamic, you want to set the Global AutoFS timeout which systemd usually honors.
Check your /etc/autofs.conf (or /etc/default/autofs on older Debian). Look for: code Ini
timeout = 300
Change it to: code Ini
timeout = 3600 # 1 hour
Then restart: systemctl restart autofs
Since sssd_autofs is starting and stopping, ensure the responder stays alive a bit longer to avoid the "startup delay" when a user clicks. In /etc/sssd/sssd.conf: code Ini
[autofs]
idle_timeout = 3600
close 777 on webclients gssproxy read by www-data is enough, better switch to root alltogether