
Magento 2 maintenance mode is a file switch. bin/magento maintenance:enable creates var/.maintenance.flag, and while that file exists every request returns a 503 page, except requests from IP addresses listed in var/.maintenance.ip. Add those with –ip or maintenance:allow-ips. The page itself is rendered from pub/errors, so you customize it there, not in your theme.
That simplicity hides a few traps. An IP allowlist that works on a single server stops working the moment the store sits behind a load balancer or CDN. Varnish keeps serving cached pages to some customers while others see the 503. Queue consumers keep writing to the database during a deploy that assumed the store was quiet. And the default 503 page looks nothing like the brand. This guide covers how maintenance mode is implemented, every command and file involved, the proxy problem, and how to ship a proper maintenance page on Magento and Adobe Commerce.
How maintenance mode works under the hood
The implementation lives in Magento\Framework\App\MaintenanceMode, and it is short enough to read in a few minutes on GitHub. The key facts:
- The flag file is named
.maintenance.flagand lives in thevardirectory. Turning maintenance on touches that file. Turning it off deletes it. - The allowlist is a second file,
.maintenance.ip, in the same directory. - On each request, the application bootstrap asks whether maintenance is on for the client’s remote address. If the flag file does not exist, the answer is no immediately, which is why the check is cheap.
- If the flag exists, each entry in the allowlist is compared to the client address. An exact match lets the request through. Entries that are valid ranges are also checked, so a CIDR range such as
192.0.2.0/24works as well as a single address. - Everyone else gets HTTP 503 and the maintenance page.
- Changing the mode dispatches a
maintenance_mode_changedevent, which modules can observe.
Because the switch is just a file, you can turn maintenance on without running PHP at all, by creating var/.maintenance.flag directly. That is useful when bin/magento itself is broken mid-deploy. Remember that on a multi-node setup, var must be shared, or each node needs its own flag.
The commands
Adobe’s maintenance mode guide documents four commands.
| Command | What it does | Files it touches |
|---|---|---|
bin/magento maintenance:enable |
Turns maintenance on | Creates var/.maintenance.flag |
bin/magento maintenance:enable --ip=192.0.2.10 --ip=192.0.2.11 |
Turns it on and sets exempt addresses | Creates the flag, writes var/.maintenance.ip |
bin/magento maintenance:disable |
Turns maintenance off | Deletes the flag |
bin/magento maintenance:status |
Shows the current maintenance status | Reads the flag and allowlist |
bin/magento maintenance:allow-ips 192.0.2.10 192.0.2.11 |
Replaces the exempt list without changing the mode | Writes var/.maintenance.ip |
bin/magento maintenance:allow-ips --none |
Clears the exempt list | Deletes var/.maintenance.ip |
A few behaviors worth knowing:
- Repeat
--iponce per address.maintenance:allow-ipstakes a space-separated list instead. - Passing
--iptomaintenance:disablesaves the list for the next time maintenance is turned on. --ip=noneonmaintenance:enableclears the list, the same asallow-ips --none.allow-ipsreplaces the list each time. To add one address, include the existing ones in the same command.
A typical deploy wrapper looks like this:
bin/magento maintenance:enable --ip=203.0.113.5
# stop queue consumers, deploy code, run setup:upgrade, import config
bin/magento maintenance:disable
Stop queue consumers when you enable maintenance
Maintenance mode blocks HTTP requests. It does not stop processes already running on the server. Adobe’s guide is explicit that after enabling maintenance mode you should stop all message queue consumers, finding them with ps -ef | grep queue:consumers:start and killing each process, and that on a multi-node environment you repeat this on every node.
Cron is the other process to think about. A cron run that starts mid-deploy can execute against half-updated code. Many teams remove or pause the crontab entry for the duration of the maintenance window and restore it afterwards. Our guide to message queue consumers with Supervisor and cron_consumers_runner covers how consumers are started, which tells you what you need to stop.
Why your IP allowlist does not work behind a proxy
This is the most common maintenance mode complaint: the developer’s IP is in the allowlist, but they still see the 503.
The cause is how Magento finds the client address. The bootstrap uses Magento\Framework\HTTP\PhpEnvironment\RemoteAddress, which by default reads REMOTE_ADDR. Behind a load balancer, Varnish, or a CDN, REMOTE_ADDR is the proxy’s address, not the visitor’s. Magento compares the proxy address to your allowlist, finds no match, and serves the 503.
| Setup | What REMOTE_ADDR contains | Allowlist works by default? |
|---|---|---|
| Single server, direct traffic | The visitor’s IP | Yes |
| Behind a load balancer | The load balancer’s IP | No |
| Behind Varnish on the same host | 127.0.0.1 or the Varnish host | No |
| Behind a CDN such as Cloudflare or Fastly | A CDN edge IP | No |
There are three ways to fix it:
- Teach RemoteAddress to read a forwarded header. The class accepts alternative headers through dependency injection, so a small
di.xmlentry can make it read a header such asHTTP_X_FORWARDED_FORor the header your CDN sets. In recent 2.4 releases the class also accepts a list of trusted proxies. Only trust a forwarded header that your own proxy sets and overwrites, otherwise any visitor can spoof it. - Fix the address at the web server. Nginx’s real IP module or an equivalent can set the real client address before PHP sees the request.
- Allowlist the proxy itself, carefully. Adding the load balancer’s address does let your team in, but it also lets everyone in, because every request arrives from that address. Avoid this.
Whichever you choose, test it before the maintenance window, not during it.
Varnish, CDNs, and cached pages
Maintenance mode is enforced by Magento. A request that never reaches Magento never sees it. If Varnish or a CDN holds a cached copy of a page, it can keep serving that copy while the flag is on. Customers on cached category pages may keep browsing while customers who add to cart hit the 503. That inconsistency is confusing, and it can let orders start against a store that is mid-upgrade.
Two practical options:
- Accept it for short windows. For a quick deploy, serving cached pages briefly is often better for customers than a blank 503. Make sure cart and checkout requests, which are not cached, fail cleanly.
- Serve the maintenance page at the edge. For longer windows, switch the maintenance response at Varnish or the CDN so every visitor sees the same page. Our guide to full page cache and Varnish on Hyva explains how caching decides what to serve.
Also note what search engines see. A 503 tells crawlers the outage is temporary, which is the right signal. The default error processor we reviewed sets the 503 status but does not add a Retry-After header, so if you want one, add it in your custom page or at the web server.
Building a custom 503 page
The maintenance page is not rendered by your theme. It comes from the standalone error processor in pub/errors, which runs even when the application cannot fully boot. The processor’s process503() method sets the 503 status and renders 503.phtml from the active skin.
The skin is chosen in pub/errors/local.xml. Magento ships a local.xml.sample whose first setting is <skin>default</skin>. To build a branded page without touching core files:
- Copy
pub/errors/defaultto a new folder, such aspub/errors/brand. - Copy
pub/errors/local.xml.sampletopub/errors/local.xml. - Set
<skin>brand</skin>inlocal.xml. - Edit
pub/errors/brand/503.phtmland its CSS. - Commit the folder and
local.xmlso every environment uses the same page.
If a template is missing from your custom skin, the processor falls back to the default skin, so you only need to copy the files you change.
| Approach | Where | Survives upgrades | Works if the app cannot boot |
|---|---|---|---|
Edit pub/errors/default directly |
Core folder | No, overwritten | Yes |
Custom skin in pub/errors with local.xml |
Your own folder | Yes | Yes |
| Page served by the web server or CDN | Nginx, Varnish, or CDN config | Yes | Yes, even if PHP is down |
| Theme template | app/design |
Not applicable | No, themes are not used for this page |
Some practical rules for the page itself:
- Keep it self-contained. Inline the CSS and use only images inside the skin folder. Do not load assets from
pub/static, because static content may be mid-deploy. On a Hyva store, this means the page cannot depend on the theme’s compiled Tailwind file. Write a few lines of plain CSS instead. - Say when you expect to be back if you know, and give a contact route for urgent orders.
- Localize if you need to. Adobe’s guide notes that a
skinparameter can be passed through rewrite rules to show a localized 503 page per store, using separate skin folders. - Do not expose details. The same processor renders error reports. In
local.xml, thereportsection controls whether exception details print on screen. Production should never print traces to visitors.
Maintenance mode in a deploy pipeline
How much maintenance time you need depends on what the deploy changes. Adobe’s pipeline guidance runs app:config:import and, when schema changed, setup:upgrade --keep-generated with maintenance on. Code compilation and static content deploy can run on a build system before the window starts, which keeps the window short. Our article on Magento 2 deploy modes and setup:di:compile explains why those steps can move out of the window.
| Deploy contains | Maintenance needed? | Why |
|---|---|---|
| Template or CSS change only | Usually no, with atomic releases | Nothing touches the database |
| Config change in config.php | Briefly | app:config:import writes to the database |
| New module or schema change | Yes | setup:upgrade changes tables and data |
| Magento version upgrade | Yes, and plan longer | Schema, data patches, and reindexing |
For stores where every minute of downtime matters, atomic release deploys keep the window to the database steps alone. Our guide to zero-downtime Magento deploys with Deployer shows how releases, shared paths, and the switch to the new release fit together. If you share var between releases, the maintenance flag lives there too, so it carries across the switch.
Where Bemeir fits
Maintenance windows are where deploy process, hosting, caching, and storefront design meet. A Hyva build is a good moment to replace the default 503 page and document the deploy runbook at the same time. The same planning applies to storefronts on Shopify, Shopware, or BigCommerce, each with its own maintenance controls. You can learn more about Bemeir, see our technology partners, or start at the Bemeir home page.
FAQ
How do I put Magento 2 in maintenance mode but still access the site myself?
Run bin/magento maintenance:enable –ip= followed by your public IP address, repeating –ip for each address. Magento writes them to var/.maintenance.ip and lets those requests through. If the store is behind a load balancer or CDN, configure Magento to read the real client IP first, or the allowlist will not match.
Where is the Magento maintenance flag file?
It is var/.maintenance.flag in the Magento root. If the file exists, maintenance mode is on. The exempt addresses are in var/.maintenance.ip. You can create or delete the flag file by hand if bin/magento is not working, but on multi-node setups make sure var is shared or repeat it on each node.
Why do I still see the 503 page even though my IP is allowlisted?
Usually because Magento sees a proxy address instead of yours. Behind a load balancer, Varnish, or a CDN, REMOTE_ADDR holds the proxy IP. Configure RemoteAddress to read a trusted forwarded header, or set the real client IP at the web server, then test with maintenance:status and a request from your address.
How do I customize the Magento 2 maintenance page?
Copy pub/errors/default to a new folder, copy pub/errors/local.xml.sample to local.xml, and set the skin element to your folder name. Then edit 503.phtml in your folder. Keep the page self-contained with inline CSS, because the theme and static files are not available to it.
Does maintenance mode stop cron and queue consumers?
No. Maintenance mode only blocks web requests. Adobe recommends stopping all queue consumers after enabling it, on every node. Many teams also pause the crontab during the window so a cron run does not start against half-deployed code, then restore it after maintenance:disable.




