Guides
Security Model
XAXA can change anything on your server, so it is designed to keep a bug in the panel — or in one of your sites — from becoming a full server compromise.
The panel never runs as root
- The web UI runs as the unprivileged
xaxauser in its own PHP-FPM pool, behind nginx on port 8443. - The panel code in
/opt/xaxais owned by root and read-only forxaxa. Onlystorage/andbootstrap/cache/are writable; panel data lives in/var/lib/xaxa/panel. - The panel's PHP-FPM service runs with
ProtectSystem=trueinstead of the package defaultfull:/usr,/bootand/efistay read-only and the other hardening options are kept, while/etcbecomes writable for the root agent (started through sudo), so it can manage users, groups and service configuration. Panel and site users are still limited by file permissions; this does not give them write access to/etc.
One door to root: the agent
Privileged work goes through /usr/local/sbin/xaxa-agent, a single root-owned PHP script. The sudoers rule allows xaxa to run exactly this file and nothing else:
xaxa ALL=(root) NOPASSWD: /usr/local/sbin/xaxa-agent
The agent:
-
does not load Laravel or any file the panel can write,
-
has a fixed list of actions (
site.create,ssl.issue,db.create, …), -
re-validates every input itself: domain and user-name patterns, PHP version allow-list, port ranges, IP/CIDR syntax, cron expressions, password character sets,
-
only touches Linux users in the
xaxa-sitesgroup — neverrootor system users, -
runs commands as argument arrays with
proc_open, never through a shell, -
never writes as root into a site user's home directory (it uses
runuserso symlink tricks can only hurt the site user), -
rejects dangerous nginx directives in vhosts (module loading, scripting, includes outside the site's folder, log paths outside
/var/log/nginx, certificate or password files outside the site's own folders) and keeps every vhost inside its own site: document roots in the site's home, PHP only through the site's own pool,server_namelimited to the site's domains, ports 80/443 only, no..or request values in file paths (try_files,index,rewrite,error_page). The raw vhost editor is admin-only, because nginx can read every site's files. Vhosts are tokenized exactly the way nginx reads them — quotes, escapes and#comments included — so a directive cannot be hidden inside a string or comment. -
never puts a secret on a command line (every local user can read process arguments): passwords go to child processes through their environment, stdin or private files.
Every call is written to /var/log/xaxa-agent.log (root only): time, action, calling user, result and duration. Passwords, keys, file contents and environment values are masked. Admins can read it under Events → Root agent.
On an installed server you can read the whole agent at /usr/local/sbin/xaxa-agent.
Sites are isolated from each other
- Each site has its own Linux user and
750home directory. - PHP sites run in a per-site PHP-FPM pool as that user, isolated by Linux permissions (
750home directories,0711/home). - Node.js and Python apps run as systemd services under the site user with
NoNewPrivileges,ProtectSystem=fulland a private/tmp. - Redis is shared, but each site can get its own Redis user that only sees keys starting with its name; new servers require such a user (see Databases → Redis).
- The SMTP login in
/etc/msmtprcis readable by every site user so sites can send mail. Give the sites a login of their own (Instance → Mail → Site login), so the panel's alerts are sent with a login no site can read; otherwise use a credential meant only for sending. - A site's Linux user also names its Redis user and its
xaxa-<user>service, PHP pool and files, so a new site cannot use a name the server already has (Redisdefault,xaxa-admin, the panel's own workers and pools). - No site can take the panel's domain, and the panel cannot take the domain of a site.
phpMyAdmin and the web terminal
- Both run on port 2083, a separate browser origin from the panel: a script running inside phpMyAdmin (for example through a bug that shows database content as HTML) cannot read panel pages or their CSRF token.
- Every request goes through the panel session gate. phpMyAdmin also needs a temporary database login that the same account opened from the panel (one database, 60 minutes), and only those temporary logins can sign in there.
- The terminal program (ttyd) runs as the site user, not root, on a socket only nginx can open; it accepts WebSockets only from its own origin.
- Signing out, changing the password, losing a site or a deleted account closes open terminals and drops temporary database logins at once.
- phpMyAdmin runs as its own user, outside the
www-datagroup, so it cannot connect to the PHP-FPM pools of the panel or of sites.
Updates
xaxa update runs the installer from inside the release package after checking its minisign signature with the public key saved on the server at install time. The installer on the website is used only for the first install.
- nginx logs are written to root-owned
/var/log/nginx/xaxa, not into the site's home. - Every
mysqlclient the agent runs as root hasLOAD DATA LOCALturned off, so an imported or restored dump cannot read a file of the server.
Accounts and sessions
- The first admin is created via a one-time setup link printed by the installer — no "first visitor becomes admin" window.
- Passwords are hashed with bcrypt; TOTP two-factor authentication is built in.
- Login and 2FA attempts are rate-limited per IP and per account (10 wrong passwords or 5 wrong codes in 15 minutes). The panel trusts no proxy header, so a request from the server itself cannot pose as another IP. Actions that ask for the password again (changing it, turning 2FA off, new recovery codes, API tokens, signing out sessions, deleting a site) allow 10 tries a minute per account. Failed attempts are written to
storage/logs/auth.log. A fail2ban jail (xaxa-panel) bans an IP on port 8443 and 443 (the panel domain) after repeated failures. - Account & 2FA → Sessions lists the signed-in browsers with their address and sign-in time. Sign out one of them, or all others, with your password. Failed sign-ins of the last 30 days are listed there too, and a sign-in from an address the account has not used before sends an alert.
- API tokens can expire (30/90/365 days), can be limited to deployments and to some sites, and need the password when they can change things or never expire. Deleting an account deletes its tokens.
- The alert webhook URL and its signing secret are stored encrypted.
- Every change is recorded in the audit log.
- The panel's access log masks secret links (the one-time setup link and deploy webhook URLs).
- SSH, database and basic-auth passwords are shown once and not stored by the panel. Credentials from a one-click install or a clone (database user, WordPress admin) stay on the site's Overview, encrypted with the app key, until you dismiss them. The S3 secret key lives only in a root-only rclone config, and off-site copies are encrypted before upload with a password shown once.
Server hardening
The installer enables UFW (deny incoming by default), fail2ban for SSH and the panel login, and unattended security upgrades — including PHP packages from the Sury/Ondřej repository, which the default configuration would skip. Node.js and Python versions receive patch updates weekly.
What admins can do
Panel admins are effectively root: they can, for example, restore backups over a site or open firewall ports. Protect admin accounts with 2FA and give teammates the user role when they only need to manage sites.
Reporting a vulnerability
Please report security issues privately to security@xaxa.com.tr. Do not post them publicly.
Found a mistake or need help? support@xaxa.com.tr