The default error pages from a web server are plain, technical and sometimes reveal the server version. Custom error pages keep visitors on your site with your branding, a search box and helpful links, while still returning the correct HTTP status code to browsers and search engines.
Before you start
- An error page per status, saved as static HTML (e.g.
/errors/404.html). Static files keep working even when PHP or the database is down. - Use absolute paths for CSS and images inside error pages (
/assets/style.css), because they are shown at any URL. - On cPanel hosting you can use cPanel → Advanced → Error Pages instead.
Which error pages to create
| Code | Meaning | What to show |
|---|---|---|
| 404 | Not Found | Search box, links to popular pages |
| 403 | Forbidden | Short explanation and a contact link |
| 500 | Internal Server Error | Apology, "try again shortly" |
| 502 / 504 | Bad Gateway / Gateway Timeout | Same as 500 |
| 503 | Service Unavailable | Maintenance message and expected return time |
Apache and .htaccess: ErrorDocument
In the vhost or in .htaccess (works on LiteSpeed too):
ErrorDocument 404 /errors/404.html
ErrorDocument 403 /errors/403.html
ErrorDocument 500 /errors/500.html
ErrorDocument 503 /errors/503.htmlWarning: use a local path, not a full URL. ErrorDocument 404 https://example.com/404.html makes Apache send a 302 redirect, so search engines see a redirect instead of a 404 (a "soft 404").
Nginx: error_page
error_page 404 /errors/404.html;
error_page 500 502 504 /errors/500.html;
error_page 503 /errors/503.html;
location ^~ /errors/ {
internal;
root /var/www/example.com;
}internalstops visitors from loading the pages directly at/errors/....- To show custom pages for errors coming from PHP-FPM or a proxied app, add
fastcgi_intercept_errors on;orproxy_intercept_errors on;. Otherwise Nginx passes the app's own error body through. - Don't write
error_page 404 = /index.php;unless you mean it: the=lets the target's status (usually 200) replace the 404.
A maintenance 503 page
For planned work, return 503 with Retry-After so search engines come back later instead of indexing the maintenance page.
Nginx
if (-f /var/www/example.com/maintenance.on) { return 503; }
error_page 503 @maintenance;
location @maintenance {
root /var/www/example.com/errors;
rewrite ^ /503.html break;
add_header Retry-After 3600 always;
}Create maintenance.on to switch it on, delete it to switch it off; no reload needed.
Apache / .htaccess
RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/maintenance.on -f
RewriteCond %{REQUEST_URI} !^/errors/
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.10$
RewriteRule ^ - [R=503,L]
ErrorDocument 503 /errors/503.html
Header always set Retry-After "3600" "expr=%{REQUEST_STATUS} == 503"The REMOTE_ADDR line lets your own IP through to test the site.
Check the status codes
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/this-does-not-existIt must print 404, not 200 or 302, while the browser shows your custom page.
Troubleshooting
- Custom page never shows: the app (WordPress, Laravel) handles 404s itself; customise its theme template instead, or enable intercept_errors in Nginx.
- Error page without styles: relative asset paths. Use absolute paths.
- 500 page itself fails: it depends on PHP. Keep error pages static.
FAQ: custom error pages
Are custom 404 pages good for SEO?
Yes, as long as they return a real 404 status. They also reduce bounce rate.
Should I redirect all 404s to the homepage?
No. Search engines treat that as soft 404s. Redirect only moved pages, with 301s.
Can I show different error pages per site?
Yes. Define them in each vhost, server block or site's .htaccess.
