0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
JPPINTO
  • Home
  • Blog
  • Certifications
  • About
  • Contact
  • Shop
  • Gallery
  • Current Setup
Contact

Search

July 3, 2026 / Linux, Servers, Ubuntu

Create an Nginx Default Catch-All Site on Ubuntu

Tags: linux, nginx, server setup, ssl, ubuntu, web server
Featured image for Create an Nginx Default Catch-All Site on Ubuntu

When Nginx receives a request for an unknown domain, it may serve the first enabled site if there is no explicit default server. On a server with multiple WordPress sites or web apps, that can expose the wrong site for a domain that is not configured.

A default catch-all site fixes this by handling unmatched HTTP and HTTPS requests before they fall through to a real site.

What the Script Does

The deployment script this article is based on:

  • Creates /etc/nginx/sites-available/000-catch-all.conf.
  • Enables it with a symlink in /etc/nginx/sites-enabled/.
  • Removes Ubuntu's default enabled site if present.
  • Supports a dry run mode.
  • Requires confirmation before making changes.
  • Backs up existing files.
  • Runs nginx -t.
  • Rolls back if validation fails.
  • Reloads Nginx only after validation succeeds.

Why a Catch-All Site Helps

Without a catch-all, an unknown hostname can accidentally show whichever Nginx server block is first.

That can cause problems such as:

  • A staging site responding to the wrong hostname.
  • A WordPress site appearing for unrelated DNS records.
  • SSL confusion when an unmatched HTTPS request reaches a real site.
  • Extra noise in access logs.

The catch-all gives Nginx a deliberate default.

Create the Catch-All Config

Create the file:

sudo nano /etc/nginx/sites-available/000-catch-all.conf

Add:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    ssl_reject_handshake on;
}

For HTTP, return 444 tells Nginx to close the connection without sending a normal response.

For HTTPS, ssl_reject_handshake on; rejects unmatched TLS handshakes instead of serving a certificate for the wrong site.

Enable the Catch-All Site

Create the symlink:

sudo ln -sfn /etc/nginx/sites-available/000-catch-all.conf /etc/nginx/sites-enabled/000-catch-all.conf

If the default Ubuntu site is still enabled, back it up and remove the enabled symlink:

sudo mkdir -p /opt/server-backups/nginx
timestamp="$(date +%Y%m%d%H%M%S)"

if [ -e /etc/nginx/sites-enabled/default ] || [ -L /etc/nginx/sites-enabled/default ]; then
    sudo cp -a /etc/nginx/sites-enabled/default "/opt/server-backups/nginx/sites-enabled-default.before.${timestamp}.bak"
    sudo rm -f /etc/nginx/sites-enabled/default
fi

Validate and Reload

Test the config:

sudo nginx -t

If validation succeeds, reload Nginx:

sudo systemctl reload nginx

If validation fails, remove the catch-all symlink and restore your backup before reloading.

Safer Script Pattern

For production servers, use a script pattern with explicit flags:

sudo bash ./fix-nginx-catch-all.sh --dry-run
sudo bash ./fix-nginx-catch-all.sh --confirm

A --dry-run mode lets you preview changes. A --confirm flag prevents accidental changes when someone runs the script without reading it.

You can also support a no-reload option:

sudo bash ./fix-nginx-catch-all.sh --confirm --no-reload

That is useful if you want a deployment system to validate now and reload Nginx later.

Full Script

Here is the full Bash script used for this Nginx default catch-all workflow:

#!/usr/bin/env bash

__show_script_usage() {
  cat <<'__SCRIPT_USAGE__'
# Fix-NginxDefaultCatchAll.sh

Creates an explicit Nginx default catch-all vhost so unknown domains do not fall through to the first WordPress site.

## Example Usage

Preview:

```bash
cd /opt/DevOps/Scripts/ServerDeployment
sudo bash ./Fix-NginxDefaultCatchAll.sh --dry-run
```

Apply:

```bash
sudo bash ./Fix-NginxDefaultCatchAll.sh --confirm
```

Apply without reloading Nginx:

```bash
sudo bash ./Fix-NginxDefaultCatchAll.sh --confirm --no-reload
```

## Notes

This writes `/etc/nginx/sites-available/000-catch-all.conf`, enables it as `/etc/nginx/sites-enabled/000-catch-all.conf`, disables Ubuntu's default enabled site if present, runs `nginx -t`, and reloads Nginx only after validation succeeds.
__SCRIPT_USAGE__
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  case "${1:-}" in
    -h|--help|--usage)
      __show_script_usage
      exit 0
      ;;
  esac
fi

set -euo pipefail

CATCH_ALL_AVAILABLE="/etc/nginx/sites-available/000-catch-all.conf"
CATCH_ALL_ENABLED="/etc/nginx/sites-enabled/000-catch-all.conf"
UBUNTU_DEFAULT_ENABLED="/etc/nginx/sites-enabled/default"
BACKUP_DIR="/opt/DevOps/Backups/Nginx"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"

DRY_RUN="false"
CONFIRM="false"
RELOAD_NGINX="true"

while [[ "$#" -gt 0 ]]; do
    case "$1" in
        --dry-run)
            DRY_RUN="true"
            shift
            ;;
        --confirm)
            CONFIRM="true"
            shift
            ;;
        --no-reload)
            RELOAD_NGINX="false"
            shift
            ;;
        -h|--help|--usage)
            __show_script_usage
            exit 0
            ;;
        *)
            echo "ERROR: Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

if [[ "${DRY_RUN}" != "true" && "${CONFIRM}" != "true" ]]; then
    echo "ERROR: Use --dry-run to preview or --confirm to apply." >&2
    exit 1
fi

if [[ "${EUID}" -ne 0 ]]; then
    echo "ERROR: Please run this script with sudo." >&2
    exit 1
fi

log() {
    printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}

run() {
    if [[ "${DRY_RUN}" == "true" ]]; then
        printf 'DRY RUN: %s\n' "$*"
    else
        "$@"
    fi
}

write_catch_all_config() {
    if [[ "${DRY_RUN}" == "true" ]]; then
        cat <<EOF
DRY RUN: write ${CATCH_ALL_AVAILABLE}:
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    ssl_reject_handshake on;
}
EOF
        return
    fi

    cat > "${CATCH_ALL_AVAILABLE}" <<'EOF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    ssl_reject_handshake on;
}
EOF
}

rollback() {
    log "Rolling back catch-all changes."

    if [[ -n "${CATCH_ALL_BACKUP:-}" && -e "${CATCH_ALL_BACKUP}" ]]; then
        cp -a "${CATCH_ALL_BACKUP}" "${CATCH_ALL_AVAILABLE}"
    else
        rm -f "${CATCH_ALL_AVAILABLE}"
    fi

    rm -f "${CATCH_ALL_ENABLED}"

    if [[ -n "${ENABLED_BACKUP:-}" && -e "${ENABLED_BACKUP}" ]]; then
        cp -a "${ENABLED_BACKUP}" "${UBUNTU_DEFAULT_ENABLED}"
    fi
}

if [[ "${DRY_RUN}" == "true" ]]; then
    log "Previewing Nginx default catch-all remediation."
else
    log "Applying Nginx default catch-all remediation."
    mkdir -p "${BACKUP_DIR}"
fi

if [[ -e "${CATCH_ALL_AVAILABLE}" ]]; then
    CATCH_ALL_BACKUP="${BACKUP_DIR}/000-catch-all.conf.before.${TIMESTAMP}.bak"
    run cp -a "${CATCH_ALL_AVAILABLE}" "${CATCH_ALL_BACKUP}"
fi

if [[ -e "${UBUNTU_DEFAULT_ENABLED}" || -L "${UBUNTU_DEFAULT_ENABLED}" ]]; then
    ENABLED_BACKUP="${BACKUP_DIR}/sites-enabled-default.before.${TIMESTAMP}.bak"
    run cp -a "${UBUNTU_DEFAULT_ENABLED}" "${ENABLED_BACKUP}"
    run rm -f "${UBUNTU_DEFAULT_ENABLED}"
else
    log "No enabled Ubuntu default site found at ${UBUNTU_DEFAULT_ENABLED}."
fi

write_catch_all_config
run ln -sfn "${CATCH_ALL_AVAILABLE}" "${CATCH_ALL_ENABLED}"

if [[ "${DRY_RUN}" == "true" ]]; then
    log "Dry run complete. Re-run with --confirm to apply."
    exit 0
fi

log "Testing Nginx configuration."
if ! nginx -t; then
    rollback
    echo "ERROR: nginx -t failed. Changes were rolled back." >&2
    exit 1
fi

if [[ "${RELOAD_NGINX}" == "true" ]]; then
    log "Reloading Nginx."
    systemctl reload nginx
else
    log "Skipping Nginx reload because --no-reload was provided."
fi

log "Nginx default catch-all remediation complete."

Test the Result

After reloading Nginx, test an unmatched HTTP host:

curl -I -H "Host: unknown.example.com" http://127.0.0.1/

For HTTPS, test with a hostname that should not match a real site:

curl -k -I --resolve unknown.example.com:443:127.0.0.1 https://unknown.example.com/

The exact client output can vary, but the important result is that the request should not serve one of your real websites.

Quick Reference

sudo nginx -t
sudo ln -sfn /etc/nginx/sites-available/000-catch-all.conf /etc/nginx/sites-enabled/000-catch-all.conf
sudo systemctl reload nginx

A default catch-all site is a small Nginx hardening step. It makes unmatched domains fail intentionally instead of drifting into the first configured website.

Post Views: 147
<- Install and Configure Redis on Ubuntu for Local Object Cache
Configure UFW Firewall on Ubuntu for Web Servers ->

Categories

  • Active Directory (5)
  • AI (3)
  • Amazon Cloud Services (1)
  • AWS (2)
  • Blazor (1)
  • C# (C-Sharp) (3)
  • CI/CD Pipelines (1)
  • Cloud (1)
  • Cloudflare (2)
  • Codex (1)
  • Containers (4)
  • Deployment (2)
  • Development (5)
  • DNS (1)
  • Docker (3)
  • Email (1)
  • Family (1)
  • General (5)
  • IIS 6.0 (4)
  • IIS 7.0 (10)
  • IIS 8.0 (1)
  • Infrastructure as Code (IaC) (1)
  • Kubernetes (3)
  • Linux (9)
  • Microsoft 365 (2)
  • MySQL (1)
  • Office 2010 (1)
  • PHP (1)
  • PowerShell (11)
  • Productivity (1)
  • Security (1)
  • Servers (9)
  • SharePoint 2007 (8)
  • SharePoint 2010 (19)
  • SharePoint 2013 (2)
  • SharePoint Online (1)
  • SMTP (4)
  • SQL Server 2008 (1)
  • SQL Server 2008 R2 (1)
  • SQL Server 2012 (2)
  • SQL Server 2019 (1)
  • SSL (1)
  • Travel (1)
  • Troubleshooting (1)
  • Ubuntu (9)
  • Uncategorized (1)
  • URL Rewrite (2)
  • Visual Studio 2019 (1)
  • Visual Studio Code (1)
  • Windows 10 (7)
  • Windows 2003 (9)
  • Windows 2008 (18)
  • Windows 2012 (6)
  • Windows 7 (3)
  • Windows Firewall (1)
  • Windows Vista (1)
  • WordPress (3)
  • WP-CLI (3)

Recent Posts

  • GPT-5.6 Sol Changes How We Prompt—and How We Write AGENTS.md
  • Protect a Domain That Does Not Send Email with SPF, DMARC, and Null MX
  • Turning Our Atlanta Vacation Photos into a Video with PowerShell
  • Bulk Create Cloudflare Origin CA Certificates with PowerShell
  • Test a Cloudflare Global API Key Connection with PowerShell

Advertisement

Tags

agents.md ai coding agents aws bash cloudflare cloud storage codex context engineering developer workflow dev to production dns externalize blob externalize sharepoint data full installation http redirect https IIS IIS installation index server configuration installing cumulative updates linux load balance central administration microsoft 365 nginx powerpoint powershell redirect http to https s3 server setup sharepoint 2010 cumulative updates sharepoint 2010 farm build sharepoint 2010 farm configuration sharepoint 2010 farm installation sharepoint data externalization SMTP ssl storagepoint ubuntu web server windows Windows 7 windows server 2008 wordpress wp-cli x86
© 2026 JPPinto.com. All rights reserved.