NGINX: Step-by-Step Guide to Centralise Custom Error Pages for All Virtual Hosts

When managing multiple websites on a single NGINX server, maintaining consistent and professional error pages is essential for both user experience and operational efficiency. Rather than duplicating error page configurations across each virtual host, a centralised approach simplifies maintenance, improves consistency, and reduces configuration errors.

This article explains how to configure NGINX to serve custom error pages from a single centralised location. The method ensures that all virtual hosts (server blocks) on your server reference a common set of error pages, making it easier to update messages and apply consistent branding or formatting across your entire web infrastructure.

Step 1: Create Error Directory #

Create a directory to store centralised error pages:

bash
sudo mkdir -p /var/www/html/errors

Step 2: Create HTML Error Pages #

Create minimal, self-contained HTML files for each error code

What This Means

  • Minimal: Only include the essential content (a title and message).
  • Self-contained: Do not use external files (no linked CSS, JS, or images). This ensures the page always loads, even if the rest of your website is broken.

401.html – Unauthorized #

Create page:

bash
sudo vim /var/www/html/errors/401.html

Add:

/var/www/html/errors/401.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>401 – Unauthorized</title>
</head>
<body>
  <h1>401 – Unauthorized</h1>
  <p>You must be authenticated to access this page or resource.</p>
</body>
</html>

403.html – Access Denied #

Create page:

bash
sudo vim /var/www/html/errors/403.html

Add:

/var/www/html/errors/403.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>403 – Access Denied</title>
</head>
<body>
  <h1>403 – Access Denied</h1>
  <p>You do not have permission to access this page or resource.</p>
</body>
</html>

404.html – Not Found #

Create page:

bash
sudo vim /var/www/html/errors/404.html

Add:

/var/www/html/errors/404.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>404 – Not Found</title>
</head>
<body>
  <h1>404 – Page Not Found</h1>
  <p>The page you're looking for doesn't exist or has been moved.</p>
</body>
</html>

500.html – Internal Server Error #

Create page:

bash
sudo vim /var/www/html/errors/500.html

Add:

/var/www/html/errors/500.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>500 – Internal Server Error</title>
</head>
<body>
  <h1>500 – Internal Server Error</h1>
  <p>Something went wrong. Please try again later.</p>
</body>
</html>

502.html – Bad Gateway #

Create page:

bash
sudo vim /var/www/html/errors/502.html

Add:

/var/www/html/errors/502.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>502 – Bad Gateway</title>
</head>
<body>
  <h1>502 – Bad Gateway</h1>
  <p>The server received an invalid response from the upstream server.</p>
</body>
</html>

503.html – Maintenance #

Create page:

bash
sudo vim /var/www/html/errors/503.html

Add:

/var/www/html/errors/503.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>503 – Site Under Maintenance</title>
</head>
<body>
  <h1>503 – Site Under Maintenance</h1>
  <p>I'm currently making updates. The site will be back shortly. Thanks for your patience.</p>
</body>
</html>

504.html – Gateway Timeout #

Create page:

bash
sudo vim /var/www/html/errors/504.html

Add:

/var/www/html/errors/504.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>504 – Gateway Timeout</title>
</head>
<body>
  <h1>504 – Gateway Timeout</h1>
  <p>The server took too long to respond. Please try again later.</p>
</body>
</html>

Step 3: Set Ownership #

Ensure www-data owns the error directory and its contents:

bash
sudo chown -R www-data:www-data /var/www/html/errors

Step 4: Configure Nginx #

Edit Main Configuration #

Open configuration file:

bash
sudo vim /etc/nginx/nginx.conf

Add inside the http block:

/etc/nginx/nginx.conf
http {
  ...

  # Global Error Pages
  error_page 401 /errors/401.html;
  error_page 403 /errors/403.html;
  error_page 500 /errors/500.html;
  error_page 502 /errors/502.html;
  error_page 503 /errors/503.html;
  error_page 504 /errors/504.html;

  # Virtual Hosts
  ...
}

Explanation:

This configures global error handling, meaning:

  • All virtual hosts (server blocks) will use these custom error pages by default.
  • You can still override them per site if needed by adding error_page directives within specific server blocks.

Create Snippet: error_pages.conf #

To avoid repeating configuration across multiple server blocks, create a reusable NGINX snippet for error page routing.

Create the snippet file:

bash
sudo vim /etc/nginx/snippets/error_pages.conf

Add:

/etc/nginx/snippets/error_pages.conf
# Serve custom error pages internally from /var/www/html/errors/

location = /errors/401.html { root /var/www/html; internal; }
location = /errors/403.html { root /var/www/html; internal; }
location = /errors/500.html { root /var/www/html; internal; }
location = /errors/502.html { root /var/www/html; internal; }
location = /errors/503.html { root /var/www/html; internal; }
location = /errors/504.html { root /var/www/html; internal; }

Explanation:

  • root /var/www/html: Specifies the root directory for the error files.
  • internal: Prevents users from directly accessing the error pages via URL. They are only served by NGINX when an error occurs.

Edit Site Configuration #

Update each site's NGINX configuration to include the custom error handling and optional maintenance mode logic.

Open site configuration file:

bash
sudo vim /etc/nginx/sites-available/<site>

Add snippet inside the server block:

/etc/nginx/sites-available/<site>
server {
  listen 443;
  ...

  # Include shared error page locations
  include snippets/error_pages.conf;

  # Maintenance flag + main handler
  location / {
    if (-f /var/www/html/errors/maintenance.flag) {
      return 503;
    }
    try_files $uri $uri/ /index.php$is_args$args;
  }

  # PHP handling
  location ~ \.php$ {
    ...
  }
}

Explanation:

  • include snippets/error_pages.conf;:
    Loads the predefined internal error page routes created earlier.
  • if (-f /var/www/html/errors/maintenance.flag) { return 503; }:
    • Checks for the presence of a file named maintenance.flag
    • If it exists, NGINX immediately returns a 503 Service Unavailable status
    • Used to quickly enable maintenance mode without modifying the server configuration

Step 5: Reload Nginx #

Test configuration and reload NGINX to apply changes:

bash
sudo nginx -t && sudo systemctl reload nginx

Step 6 (Optional): Test Maintenance Mode #

Enable maintenance mode:

bash
sudo touch /var/www/html/errors/maintenance.flag
sudo systemctl reload nginx

Visit your site in a browser. You should see your custom 503 – Site Under Maintenance page.

Disable maintenance mode:

bash
sudo rm /var/www/html/errors/maintenance.flag
sudo systemctl reload nginx