Troubleshooting
Common issues and how to recover from them. If your problem isn't listed, check app/mail_log/, your web server's error log, and the admin_audit_log table.
"License inactive" after install
Open /admin/license, paste a fresh key and click Save key. The license is re-verified live. The page is whitelisted by the runtime guard so it's always reachable, even when the rest of the app is locked.
"Network error" when scanning vault
Usually indicates HTTP 500 in assets/ajax/index.php. Check apache/logs/error.log. Typical cause: a column without AUTO_INCREMENT receiving id=0 repeatedly. Solutions already applied: security_scans and folders have auto_increment, and a previous migration fixed the JOIN-multiplication bug in encryption_keys.
Testimonial avatar missing
Verify the path starts with /assets/img/uploads/testimonials/. testimonials_list() computes avatar_url by prepending APP_URL. If APP_URL is wrong in /admin/settings#general, avatars try to load from the domain root instead of the subfolder.
Light/dark theme flickers
The inline script in pags/partials/_app_head.php applies the theme before first paint by reading localStorage. Old user preferences (without vault-theme-at timestamp) are considered stale and the admin's default wins. The flicker disappears on next visit once the user touches the toggle.
Stripe webhook not arriving
- Verify the webhook secret in
/admin/settings#paymentsmatches the one in your Stripe dashboard. - Verify the webhook URL points to
{APP_URL}/app/stripe/webhook.php. - Check the attempt log in Stripe Dashboard → Webhooks.
User stuck on "Create your PIN"
check_pin_created() reads users.pin. If the creation flow was interrupted before saving the bcrypt hash, the user always lands back on /pin?create. Fix: delete the row in DB and ask them to re-register, or set a PIN manually and send a "forgot my PIN" email.
Maintenance mode locks me out as admin
/auth, /logout and /admin/* are exempted. If you're still locked out, edit settings directly:
UPDATE settings SET setting_value='0' WHERE setting_name='maintenance_mode';
Update apply failed
The updater rolls back files automatically from the pre-update backup ZIP. The DB backup (backups/pre-update-*.sql) is left in place so you can restore it manually if a migration corrupted data:
mysql -u USER -p DBNAME < backups/pre-update-X.Y.Z-to-X.Y.Z-XXX.sql
Check the log returned by the AJAX call (also visible in /admin/license) for the exact failing statement.
"License required" lockout when API is fine
Open /admin/license — it's always reachable. Click Re-verify to refresh the cache. If still bad, paste the key fresh and save. The cache is forcibly rewritten with the new live result.
Auto-translation not working
See the dedicated i18n & auto-translation diagnostics section. The most common cause is the global pause that activates after a 429 from Google.
Cookie encryption key missing on a new install
The installer writes .fortpass-key at the project root. If you deleted it or moved files, regenerate one:
php -r "echo bin2hex(random_bytes(32));" > .fortpass-key
Make sure permissions are 0600.
Symlinked uploads folder
If you symlink assets/img/uploads/ or uploads/vault/ to a different volume for storage reasons, the updater still preserves the symlink target (it walks files, not links). Just make sure the symlink itself isn't in updater_excluded_paths()'s denylist after edits.