Your software
My Mautic version is: 7.1.2, 7.2 (multiple installations, two via Zip and one via Symphony)
My PHP version is: 8.5
My Database type and version is: MariadB
Your problem
My problem is: After a period of time, performing actions such as saving an updated email or contact fails and displays the error “CSRF token error. Try to refresh the page and try again.”
I have checked “Keep me logged in”, and have extended the PHP max_session to a large number. You appear to still be logged in, and you can navigate, but certain actions like described above fail. Because of this, after a while I have to regularly log out and log in to make sure that my work isn’t lost.
This seems to be an ongoing problem in forum postings as far back as 2018 and I don’t see any solution posted.
By PHP max_session you mean session.gc_maxlifetime ?
And you can confirm that your change to a large number is visible in /s/sysinfo ?
That’d be strange, because for us that absolutely fixes it.
Unless… maybe you have other issues, like changing IPs due to rotating firewalls or something like that?
Keep me logged in preserves the login state, but it does not by itself guarantee that the PHP session holding the CSRF token remains available. That can explain why navigation still works while form submissions fail.
The session.gc_maxlifetime check in /s/sysinfo is the first important step. Please also confirm:
The exact session.gc_maxlifetime, session.save_handler, and session.save_path values shown in /s/sysinfo.
Whether the application runs behind a reverse proxy, CDN, load balancer, or more than one PHP/web container.
Whether the session store is persistent and shared between all application instances. File-based PHP sessions inside ephemeral containers commonly cause this kind of behaviour.
That the proxy/CDN does not cache any authenticated /s/ pages or responses, and forwards Cookie and Set-Cookieheaders unchanged.
Whether the client IP can change during the session, for example because of a rotating VPN, firewall, or proxy.
When the error happens, please compare the failing request with a successful one in browser DevTools → Network:
confirm whether the session cookie value is the same;
check whether the response sends a new Set-Cookie header;
record the exact timestamp and relevant application/PHP logs.
Please do not share CSRF token or session-cookie values publicly.
If the session cookie remains unchanged, the session storage is persistent, no proxy caching is involved, and the token still becomes invalid on a clean current installation, that would be strong evidence for a core issue. Otherwise, the findings should identify the session or infrastructure layer that needs adjustment.
Hi, Thanks for the detailed response. PHP values are as follows:
session.gc_maxlifetime 86400
session.save_handler user
session.save_path /var/cpanel/php/sessions/ea-php85
I am on Centurylink fiber and the WAN IP address does change periodically. I have a Ubiquiti router but strangely they do not include a WAN IP lease/change log. I can do some more experimenting and check if the IP has changed the next time that this happens. I see the cookies but don’t see the CSRF token in Google Devtools, but I’m only semi familiar with using it. I have the NGINX cache turned off on the server.
session.gc_maxlifetime = 86400 should normally be sufficient, so the lifetime setting itself is unlikely to be the main cause.
The important detail is session.save_handler = user. This means PHP is using a custom session handler, so session.save_path may not represent the actual storage location. Could you check with your hosting provider where these sessions are stored, whether the storage is persistent, and whether a cleanup process removes them earlier than 24 hours?
The CSRF token is normally not stored as a separate browser cookie. It is stored server-side in the PHP session and is sent in the form data or request body. In DevTools → Network, please inspect the failing save request and compare it with a successful one:
whether the session cookie is unchanged;
whether the request contains a _token or CSRF field;
whether the response sets a new session cookie;
the exact time of the failure.
The changing WAN IP is also worth testing, but it is not proof of the cause by itself. Please note the public IP when the session starts and when the error occurs. If the session cookie remains unchanged while only the IP changes, the hosting or proxy layer may be applying an IP-based session policy.
Since Nginx caching is disabled, caching is less likely, but this does not rule out session cleanup, a custom session handler, or a proxy/security layer. Please do not share the actual cookie or CSRF values publicly.
I have my own VPS, so I have complete access to the configuration. session.save_handler is set to “files” in the master php.ini file. I even tried adding it to the local php.ini file in the Mautic docroot, and it is still being over-ridden. So, I suspect that Mautic is setting the session.save_handler to “user” at runtime.
I will follow your suggestions regarding DevTools, however it may be a few days before I can get back on that. Thanks
Since you have full VPS access, Redis may be a good next step. The following script configures Redis as the native PHP session handler for every installed PHP version, enables Redis session locking, and uses a separate Redis database for PHP sessions.
Please review it before running:
sudo bash -s <<'EOS'
set -u
CONF_NAME="90-redis-sessions.ini"
REDIS_DATABASE="10"
echo ">> Installing Redis"
apt-get update
apt-get install -y redis-server
for phpdir in /etc/php/*; do
[ -d "$phpdir" ] || continue
version="${phpdir##*/}"
apt-get install -y "php${version}-redis" || true
for sapi in cli fpm; do
confdir="$phpdir/$sapi/conf.d"
[ -d "$confdir" ] || continue
echo ">> Writing $confdir/$CONF_NAME"
cat >"$confdir/$CONF_NAME" <<EOF
; Redis-based PHP sessions
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=${REDIS_DATABASE}"
redis.session.locking_enabled = 1
redis.session.lock_retries = -1
redis.session.lock_wait_time = 10000
session.gc_maxlifetime = 86400
EOF
done
done
systemctl enable --now redis-server
for svc in php7.4-fpm php8.0-fpm php8.1-fpm php8.2-fpm php8.3-fpm php8.4-fpm; do
if systemctl is-active --quiet "$svc"; then
echo ">> Reloading $svc"
systemctl reload "$svc" || systemctl restart "$svc"
fi
done
echo ">> Redis session configuration completed"
EOS
After running it, please verify the effective values from the web/FPM context, not only from the command line. Also check whether Mautic/Symfony explicitly configures framework.session.handler_id, as that could override the PHP session settings.
This should not modify Mautic data. It changes session storage for the installed PHP versions and reloads only active PHP-FPM services. Please ensure Redis is not exposed publicly, and do not share Redis credentials, session cookies, or CSRF tokens.
If anything goes wrong, remove the 90-redis-sessions.ini files from the PHP conf.d directories and reload the affected PHP-FPM service. PHP will then return to its previous session configuration. Existing Redis sessions will simply expire naturally.