Maintenance Windows Done Right — Planned Downtime Without the Panic
Planned maintenance shouldn't look like an outage. Picking windows, pausing monitors, 503 + Retry-After, SLA carve-outs and a comms template that works.
Want this checked continuously?
CompleteStatus grades your headers, SSL and email security 24/7 — free, commercial use allowed.
There are two kinds of downtime: the kind that happens to you, and the kind you schedule. The second kind should be boring: you picked the time, you know exactly what's changing, and you know when it ends. Yet plenty of teams manage to turn a routine database migration into something indistinguishable from an outage: users hit an unexplained error page, monitors scream, support tickets pile up, and the on-call engineer gets paged for work they're doing on purpose.
Planned maintenance done right is mostly a communication and configuration exercise, not an engineering one.
Pick a window your traffic actually agrees with
"We'll do it Sunday at 2 AM" is a guess. Your analytics know the answer:
- Find your real trough. Pull a month of traffic by hour and pick the window where requests bottom out. For a B2B tool that's often weekend mornings; for a consumer app it might be a weekday at 4 AM. If your audience spans time zones, remember that 2 AM in New York is mid-morning in Berlin.
- Check what else runs then. A classic own goal is scheduling maintenance on top of your own nightly backup, billing run or report generation. Take the database down at 3 AM and the 3 AM backup job fails too, silently.
- Avoid your own deadlines. No maintenance the night before a launch, during a marketing campaign, or on the last day of the month if your customers do end-of-month reporting against your API.
- Size the window honestly, then pad it. Estimate the work, double it, and announce that. Finishing 40 minutes early makes you look competent; overrunning by 40 minutes makes you look out of control, even if the total elapsed time is identical.
Announce it before, not during
A maintenance window that users discover by hitting an error page is just an outage with paperwork. The status page is the tool for this:
- Post the window ahead of time: a week ahead for anything customer-visible, 24–48 hours minimum. Include the start time with time zone, the expected duration, and what will be affected ("the dashboard will be unavailable; monitoring and alerting continue to run").
- Remind people as it approaches. Status page subscribers should get the scheduled notice and a "starting now" update. For API customers, an email to technical contacts is worth the effort; their integrations are the thing that breaks.
- Update when it's over. "Maintenance completed as planned" closes the loop, and gets users in the habit of checking your status page.
Pause your monitors, and make sure they unpause
If you don't suppress monitoring during the window, you train your team to ignore alerts. The 3 AM page that's "just the maintenance" this month is the real outage you sleep through next month. So: pause or schedule a maintenance window on every monitor that touches the affected systems, including the indirect ones, such as the API check that depends on the database you're migrating.
The more common failure is forgetting to unpause. A paused monitor is a blind spot with no expiry date. Teams have shipped a maintenance window, gone to bed, and run for weeks with their most important check disabled, discovering it only when a real outage went unnoticed. Two defenses:
- Use scheduled windows, not manual pauses. A window with an end time re-arms itself. A manually paused monitor waits for a human memory that has already moved on.
- If you must pause manually, create the unpause reminder at the same moment: calendar event, ticket, alarm. Pausing and scheduling the unpause are one action, not two.
Return a real 503, not a pretty 200
The maintenance page itself is where good intentions do lasting damage. A friendly "we'll be back soon!" page served with HTTP 200 tells every machine that everything is fine: search engines index your maintenance page and can replace your real content in results, API clients treat the HTML as a valid response and corrupt whatever they were syncing, and any monitoring still running records "up."
The correct answer is 503 Service Unavailable with a Retry-After header. That is what the status code exists for. It tells crawlers "temporary, come back later" (Google has long documented 503 as the right signal for planned downtime), tells well-behaved clients when to retry, and tells the truth.
server {
listen 443 ssl;
server_name app.example.com;
location / {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
root /var/www/maintenance;
rewrite ^ /index.html break;
add_header Retry-After 3600 always;
add_header Cache-Control "no-store" always;
}
}
Details worth getting right:
Retry-After: 3600: seconds until you expect to be back. An HTTP date works too. Set it to the end of your announced window.Cache-Control: no-store: the last thing you want is a CDN or browser caching the maintenance page past the window.- Keep the page self-contained. Inline the CSS and any image. If it loads assets from the app you just took down, it's a broken page, not a maintenance page.
- For APIs, return 503 with a JSON body, not HTML. Clients parsing an HTML string as JSON generate their own incident on the customer's side.
Put planned work in the SLA, in writing
If you've promised 99.9% uptime, that's about 43 minutes a month. One two-hour maintenance window blows through that almost three times over, unless your SLA says it doesn't count. Every serious SLA carves out planned maintenance, and the carve-out has teeth only if it's specific: how much notice you'll give (e.g., 72 hours), through what channel (the status page), and often a cap on total planned downtime per month. If your SLA is silent on maintenance, every window you run is technically an SLA breach you're hoping nobody measures. Fix the document before someone does.
A comms template you can steal
Subject: Scheduled maintenance — {date}, {start}–{end} {timezone}
What: {one sentence: what we're doing and why users benefit}
When: {date}, {start}–{end} {timezone} ({duration} window;
actual downtime expected to be shorter)
Impact: {what will be unavailable / degraded; what keeps working}
Action needed: {usually "none"; for API users, note retry behavior}
Updates: {status page URL} — subscribe for start/finish notices
We'll post here when maintenance begins and again when it's complete.
Send it when you schedule the window, and let the status page handle the play-by-play.
Make planned downtime boring
The goal is a window your users knew about, a page that tells machines the truth, monitors that stay quiet during the window and re-arm themselves after it. CompleteStatus supports this workflow: scheduled maintenance windows suppress alerts and resume automatically when the window ends, and hosted status pages notify subscribers, so the announcement, the "starting now" and the "all clear" live in one place. See everything included on the features page, or create a free account and schedule your next window properly.
Written with AI assistance and reviewed by the CompleteStatus team.
Get notified when CompleteStatus opens
New accounts are closed while we're in private beta. Leave your email and we'll send one message the moment sign-ups open — nothing else.