Server hardening is the unglamorous foundation under every other security control - and Magento’s documentation gives specific permissions guidance that many production stores quietly violate. Wrong permissions enable code injection through the web server; sloppy service exposure hands attackers shortcuts. This checklist is the one we run on every deployment and audit.
File Ownership and Permissions
The model: a deployment user owns the files; the web server user (www-data, nginx) gets only the access it needs:
- Directories:
750(owner rwx, group rx, other nothing) - Files:
640 - Writable paths only (
var/,generated/,pub/static/,pub/media/): group-writable (770/660with the web server in the deployment user’s group) - Never 777. A world-writable Magento directory is an open door - the classic skimmer-drop vector
Adobe documents a two-user model (magento user + web server group); the exact split matters less than the principle: the web process can write only where it must, and nowhere executable-by-design.
Production Mode and Developer Doors
bin/magento deploy:mode:set production- production mode disables the behaviours (symlinked static files, verbose errors, on-the-fly compilation) that leak information and slow the store- Block access to
setup/,update/,dev/paths at the web server - Error display off; errors to logs, report IDs to users
The Network Surface
Everything not needed publicly should not be public:
- MySQL: bound to localhost or the private subnet only
- Redis: no public binding, AUTH if networked; unauthenticated Redis on a public port is a classic full-compromise path
- OpenSearch/Elasticsearch: localhost or private network, never public
- RabbitMQ management UI: internal only
- SSH: key-only auth, no password login, ideally restricted source IPs
Application-Level Hardening
- Custom admin path + 2FA + IP allowlist (see the admin security post)
- Unused integrations and admin users disabled
- Composer credentials and secrets in environment variables or
auth.jsonoutside git -env.phppermissions locked to the deployment user - Security headers at the edge: CSP, X-Frame-Options/frame-ancestors, HSTS
The Recurring Audit
Permissions drift: a rushed deploy, a debugging session, a new module installed with sudo. Quarterly, or after any incident:
find /var/www/html -type d -perm -o+w -not -path '*/var/*' -not -path '*/pub/media/*'
find /var/www/html -type f -perm -o+w -not -path '*/var/*' -not -path '*/pub/media/*'
Both should return nothing. Hardening is a state you maintain, not a task you finish - the checklist exists so “is the store still locked down?” is a five-minute question, not a research project.