Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk11 min

How to Automate Nginx Server Block Setup on Ubuntu with a Bash Script

A production-safe Bash pattern for creating and enabling Ubuntu Nginx server blocks, validating before reload, rolling back failures, and adding HTTPS separately with Certbot.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The reliable pattern is to generate one file in /etc/nginx/sites-available, create a guarded symlink in /etc/nginx/sites-enabled, run nginx -t, and reload only after the test succeeds. A production-safe script should also validate its inputs, write atomically, make repeat runs idempotent, and restore the previous file if validation fails.

The script below provisions an HTTP site, keeps certificate issuance separate, and refuses to overwrite an unrelated configuration or symlink. Run it as root (normally with sudo) on an Ubuntu system using the distribution’s standard Nginx layout.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How Ubuntu’s Nginx site layout works

Ubuntu conventionally separates site definitions from enabled sites. The complete server-block file lives in /etc/nginx/sites-available; Nginx reads it because a matching link exists in /etc/nginx/sites-enabled. Creating the file alone does not publish the site. Canonical’s Ubuntu guidance notes that, unlike Apache, enablement is handled manually.

A server block should have a deliberate server_name. For name-based HTTP hosting, several blocks can listen on port 80 while Nginx selects the block whose name matches the request. Use distinct ports when services cannot share a listener or when you intentionally isolate them. Also consider IPv6: the paired listen [::]:80; line is needed when the host accepts IPv6 connections.

The usual request flow is:

  1. DNS points the domain to the server’s address.
  2. Nginx receives the request on the configured port.
  3. Nginx chooses a server block by the address, port, and server_name.
  4. The block maps the URL to its root and applies its location rules.

Automation should never assume that the default site is disposable. Removing /etc/nginx/sites-enabled/default is an administrative choice, not a required step in provisioning another site.

What the automation should guarantee

  • Safe inputs: reject domain names and document-root paths that contain whitespace, shell metacharacters, or unexpected syntax.
  • Atomic file creation: finish a temporary file in the same directory before replacing the live definition.
  • Idempotence: a second run produces the same intended state instead of adding duplicate links or blocks.
  • Guarded enablement: refuse to replace a non-link or a symlink aimed at another site.
  • Validation before reload: run Nginx’s configuration test and show its exact diagnostic on failure.
  • Rollback: preserve a timestamped copy before replacing an existing file.
  • Separation of concerns: provision HTTP first; request certificates only after DNS and reachability are working.

Prerequisites and decisions

Install and service state

Install Nginx from Ubuntu’s package repositories and ensure the service is present before running the script. The commands in the script use the system configuration at /etc/nginx/nginx.conf, where the standard Ubuntu configuration includes the enabled-sites directory.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose a document root

Use an absolute path such as /srv/www/example.com/public. The example script deliberately accepts a restricted path character set so a typo cannot turn into shell syntax. If an application genuinely needs spaces or other characters in a path, expand the validation rules only after designing and testing the quoting policy.

The script creates the directory with mode 0755. That makes it readable and searchable by the Nginx worker account without granting it write access. An application that must write uploads, caches, or generated files needs a separate, narrowly scoped writable directory and an ownership policy appropriate to that application; do not make the entire document root writable by the web process by default.

Decide what the domain argument means

The script treats its first argument as one DNS name and writes exactly that name into server_name. Add aliases deliberately rather than silently assuming that www. exists. Each alias must resolve to the host and be covered later by the certificate request.

A repeat-safe provisioning script

Save this as provision-nginx-site, make it executable with chmod 0755 provision-nginx-site, and invoke it with a domain and an absolute document root. It requires root so file, link, and service operations cannot succeed partially under mixed permissions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/usr/bin/env bash
set -Eeuo pipefail

usage() {
    echo "Usage: $0 DOMAIN ABSOLUTE_DOCUMENT_ROOT" >&2
    exit 2
}

[[ $# -eq 2 ]] || usage
DOMAIN=$1
DOCROOT=$2

if (( EUID != 0 )); then
    echo "Run this script with sudo or as root." >&2
    exit 1
fi

# A conservative hostname policy: letters, digits, dots and hyphens only.
if [[ ! $DOMAIN =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$ || $DOMAIN == *..* ]]; then
    echo "Invalid domain: use a DNS name without whitespace or shell metacharacters." >&2
    exit 2
fi

# Restrict paths to absolute, shell-safe names for this example.
if [[ ! $DOCROOT =~ ^/[A-Za-z0-9._/-]+$ || $DOCROOT == "/" ]]; then
    echo "Invalid document root: use a non-root absolute path containing only letters, digits, . _ - and /." &2
    exit 2
fi

SITE_DIR=/etc/nginx/sites-available
ENABLED_DIR=/etc/nginx/sites-enabled
SITE_FILE="$SITE_DIR/$DOMAIN"
ENABLED_LINK="$ENABLED_DIR/$DOMAIN"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
BACKUP_FILE="$SITE_FILE.bak.$TIMESTAMP"
TMP_FILE=$(mktemp "$SITE_DIR/.${DOMAIN}.tmp.XXXXXX")
HAD_OLD=0
LINK_CREATED=0

cleanup() {
    rm -f -- "$TMP_FILE"
}
trap cleanup EXIT

install -d -m 0755 -- "$DOCROOT"

cat > "$TMP_FILE" <<NGINX
server {
    listen 80;
    listen [::]:80;
    server_name $DOMAIN;

    root $DOCROOT;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
NGINX
chmod 0644 "$TMP_FILE"

# Refuse to replace a symlink or an unexpected object in sites-available.
if [[ -L "$SITE_FILE" || ( -e "$SITE_FILE" && ! -f "$SITE_FILE" ) ]]; then
    echo "Refusing to replace unexpected object: $SITE_FILE" &2
    exit 1
fi

if [[ -f "$SITE_FILE" ]]; then
    cp -a -- "$SITE_FILE" "$BACKUP_FILE"
    HAD_OLD=1
fi

# mv within the same directory is an atomic name replacement.
mv -f -- "$TMP_FILE" "$SITE_FILE"

# Enable only an absent link or a link that already resolves to this file.
if [[ -e "$ENABLED_LINK" || -L "$ENABLED_LINK" ]]; then
    if [[ ! -L "$ENABLED_LINK" ]]; then
        echo "Refusing to replace non-symlink: $ENABLED_LINK" &2
        if (( HAD_OLD )); then cp -a -- "$BACKUP_FILE" "$SITE_FILE"; else rm -f -- "$SITE_FILE"; fi
        exit 1
    fi
    CURRENT_TARGET=$(readlink -f -- "$ENABLED_LINK")
    EXPECTED_TARGET=$(readlink -f -- "$SITE_FILE")
    if [[ "$CURRENT_TARGET" != "$EXPECTED_TARGET" ]]; then
        echo "Refusing to change symlink $ENABLED_LINK (it points to $CURRENT_TARGET)" &2
        if (( HAD_OLD )); then cp -a -- "$BACKUP_FILE" "$SITE_FILE"; else rm -f -- "$SITE_FILE"; fi
        exit 1
    fi
else
    ln -s "../sites-available/$DOMAIN" "$ENABLED_LINK"
    LINK_CREATED=1
fi

rollback() {
    rm -f -- "$SITE_FILE"
    if (( HAD_OLD )); then
        cp -a -- "$BACKUP_FILE" "$SITE_FILE"
    fi
    if (( LINK_CREATED )); then
        rm -f -- "$ENABLED_LINK"
    fi
}

if ! nginx -t -c /etc/nginx/nginx.conf; then
    echo "Nginx rejected the generated configuration; the previous state was restored." &2
    rollback
    exit 1
fi

if ! systemctl reload nginx; then
    echo "Nginx could not be reloaded. Restoring the previous file; inspect systemctl and journal output." &2
    rollback
    exit 1
fi

echo "Enabled $DOMAIN with root $DOCROOT"
if (( HAD_OLD )); then
    echo "Backup: $BACKUP_FILE"
fi

Run it

sudo ./provision-nginx-site example.com /srv/www/example.com/public

Place an index.html in the document root, then test with a request whose Host header is the configured name. DNS can be tested independently with tools such as dig or getent hosts; a successful Nginx reload does not prove that DNS, firewall rules, or the application behind the site are correct.

Why each safety step matters

Strict mode and quoting

set -Eeuo pipefail turns unset variables, failed commands, and pipeline errors into visible failures. Every variable expansion that represents a path is quoted. The domain and path checks happen before either value is used in a filename or here-document.

Temporary file, then replacement

Writing directly to sites-available/$DOMAIN can leave a truncated configuration if the process is interrupted. mktemp creates a file in the same directory; after the complete block is written, mv replaces the name in one filesystem operation. Nginx therefore sees either the old complete file or the new complete file, not a half-written one.

Backup before replacement

On repeat runs, the old file is copied to a timestamped name before replacement. This is useful when a human has added a redirect, proxy settings, headers, or an include that the generator does not know about. Automated Nginx editing guidance also recommends backing up the configuration before changes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Symlink checks

The enabled entry must be a link to this site’s file. A pre-existing link to another target may represent a different site’s production configuration, so overwriting it would be unsafe. The script also refuses a regular file in sites-enabled, because silently replacing it would hide an administrator’s deliberate setup.

Test before reload

nginx -t checks syntax and attempts to open referenced files. It catches misspelled directives, missing certificates, invalid include paths, and permission problems before a reload is requested. The script uses -c /etc/nginx/nginx.conf explicitly; plain sudo nginx -t uses the system default and is equivalent on a standard Ubuntu installation.

Reload rather than stop and start

sudo systemctl reload nginx asks the master process to re-read a valid configuration while existing workers finish current requests. A stop/start sequence creates unnecessary downtime and is not required for a normal server-block change.

Extending the block for real applications

Multiple names

To serve aliases, add validated names to one server_name line, for example server_name example.com www.example.com;. A more advanced script should accept an array of aliases, validate every item with the same hostname policy, and escape none of them into the configuration until validation succeeds.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reverse proxy applications

The minimal try_files block serves static files and returns a 404 for missing paths. A Python, Node, or PHP application usually needs a different location that proxies to a Unix socket or localhost port. Keep that application-specific fragment in a separately reviewed template; do not make a generic generator guess the upstream address.

Permissions

Nginx workers need execute permission on every parent directory and read permission on files they serve. If a request returns 403, inspect the complete path permissions and the worker account, not only the file’s mode. If an upstream returns 502, verify that the application is listening and that the configured socket or port is reachable.

Ports and collisions

Check existing blocks before assigning a nonstandard port. Two unrelated blocks cannot both bind the same address and port unless they are intentionally configured for Nginx’s name-based routing. A duplicate default listener can make nginx -t fail or route requests to an unexpected default server.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Keep HTTPS as a separate stage

Certificate issuance introduces DNS and public-network dependencies that are unrelated to whether the generated server block is syntactically valid. Keep it outside the basic provisioning function so an ACME challenge failure does not obscure a configuration error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install Certbot

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

The second command is optional when /snap/bin is already on the administrative PATH. The host must already answer for the requested names, and port 80 must be reachable for the usual HTTP challenge.

Request and install the certificate

sudo certbot --nginx -d example.com -d www.example.com

Certbot’s Nginx plugin locates the enabled server block matching the requested domains, adds the TLS directives in place, and reloads Nginx. Review the resulting file before future generator runs: a generator that blindly rewrites it could remove certificate paths or redirect rules. Either teach the generator to preserve a managed TLS section or keep HTTP provisioning and certificate management as separate files and operations.

Renewal checks

Certbot’s packaged setup installs automated renewal through a cron job or systemd timer. Use Certbot’s renewal command with its dry-run option to test the renewal workflow without replacing a live certificate. Monitor the timer and its logs rather than assuming that initial issuance guarantees future renewal.

Testing and troubleshooting

Symptom What to check Safe next action
nginx -t reports a syntax error Read the exact file and line number in the diagnostic. Inspect the generated file and any included file named there. Repair the template or input, run nginx -t again, and do not reload until it passes.
“duplicate listen” or bind error Search enabled files for another block using the same address and port. Choose name-based routing deliberately or assign a nonconflicting port; do not delete the default site automatically.
Site returns the wrong content Confirm the request’s Host header, DNS answer, enabled symlink, and which block is the default for that listener. Correct server_name or DNS, then test and reload.
403 Forbidden Check directory traversal permissions, file modes, and the worker account’s read access. Grant only the required read/execute permissions and test the request again.
404 for an existing-looking URL Inspect root, index, and the try_files rule. Confirm the file exists beneath the configured root. Correct the path or location rule, then rerun nginx -t.
502 Bad Gateway For a proxy, check that the upstream process is running and that its socket or port matches the block. Fix the application service or upstream address; Nginx syntax alone cannot validate application health.
Reload command fails Run systemctl status nginx and inspect the service journal for the reported reason. Leave the previous active configuration in place, repair the new file, test it, and retry the reload.
Certificate request fails Verify DNS, public port 80, firewall rules, and that the requested names match an enabled block. Resolve reachability first; rerun Certbot only after the HTTP site works.

Comparing automation approaches

Approach Repeat runs Validation and recovery Best fit
Manual file and link commands Depends on the operator; duplicate or wrong links are easy to create. Requires remembering nginx -t and a backup each time. One-off experiments by an administrator.
Simple Bash generator Can be deterministic and idempotent when it checks existing files and links. Can print diagnostics, back up files, and roll back automatically as shown here. Small Ubuntu fleets with a consistent layout.
Configuration-management system Tracks declared state across many hosts and separates templates from deployment. Usually offers transaction history, but its Nginx handler still must test before reload. Multiple servers, reviews, and audited change control.
Certificate tool editing an existing block Renewal is automated after initial setup. The tool validates and reloads its own changes, but backups and template ownership still matter. Adding HTTPS after a working HTTP block.

Whichever approach you choose, retain the same invariants: validate input, preserve a rollback point, verify the enabled link, test the full Nginx configuration, and reload only after a successful test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A practical deployment checklist

  1. Confirm the Nginx package and service are installed.
  2. Choose a unique domain name and an absolute document root.
  3. Point DNS at the host and open the required firewall port.
  4. Run the provisioning script with sudo.
  5. Read the complete output from nginx -t; treat any warning or error in context.
  6. Request the site locally with the correct Host header, then test it through DNS.
  7. For an application, verify the upstream process separately from Nginx.
  8. Only after HTTP works, run Certbot for the exact names you intend to secure.
  9. Test renewal with Certbot’s dry-run workflow and keep the generated backup files according to your change-retention policy.

The Bottom Line

A safe Ubuntu Nginx automation script is more than a here-document: it owns a predictable file, a guarded enablement link, a backup, a configuration test, and a controlled reload. Keep HTTPS issuance and application health checks as separate stages, and a failed run can be corrected without taking the previously working site offline.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.