October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

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

A practical Ubuntu guide to idempotent Nginx server-block automation: validate inputs, stage and back up files, verify symlinks, test the full configuration, reload safely, and handle Certbot separately.
Length12 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Automate an Ubuntu Nginx site safely by generating a server-block file, checking the input, backing up any existing configuration, creating the sites-enabled symlink, running nginx -t, and reloading Nginx only after validation succeeds. Keep certificate issuance as a separate step so an ACME failure cannot hide a server-block error.

What the automation must do

Ubuntu’s standard multi-site layout separates configuration from enablement. A site file belongs in /etc/nginx/sites-available; Nginx reads it when a symlink with the same site name exists in /etc/nginx/sites-enabled. Ubuntu does not create that symlink for you automatically: “Unlike with Apache, you’ll need to handle the enablement manually.”

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.

A useful provisioning script therefore has two responsibilities:

  • Generate a deterministic server-block file from a domain and document-root argument.
  • Enable it without damaging an unrelated symlink, validate the complete Nginx configuration, and reload only when the test passes.

The script below also creates the document root, rejects unsafe arguments, keeps a timestamped backup, and rolls back a newly installed file if validation fails. It does not remove Ubuntu’s default site; disabling that site is a separate administrative choice.

Understand Ubuntu’s Nginx file and symlink model

sites-available stores definitions

Each site normally has one file in /etc/nginx/sites-available. A deterministic filename such as the domain itself makes repeat runs predictable: /etc/nginx/sites-available/example.com. The filename is not the value that routes a request; the server_name directive inside the file performs that matching.

sites-enabled controls activation

Ubuntu’s main Nginx configuration includes the enabled directory. An absolute symlink connects the two locations:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com

Never replace an existing link blindly. A link may point to another site’s file or to a custom configuration. The automation should accept it only when it resolves to the exact destination it intends to manage.

Names and listeners must be planned

Every block needs a deliberate, unique server_name. When several blocks are configured, choose listener addresses and ports that do not conflict with other services; the normal HTTP example listens on port 80 for IPv4 and IPv6. If two blocks use the same listener, their names still need to distinguish the hostnames that arrive on that listener.

Prerequisites and safety decisions

  • Run the provisioning command as root, usually with sudo, because it writes under /etc/nginx and reloads a system service.
  • Install Nginx and confirm its main configuration is at /etc/nginx/nginx.conf, or adjust the test command to the configuration path used by your host.
  • Choose an absolute document-root path. The example uses /srv/www/example.com/public.
  • Make sure the Nginx worker account can traverse the document-root directories and read the files your application serves. The script creates the directory but deliberately does not guess an ownership policy for your application.
  • Have DNS point the requested name at the server and allow the intended HTTP port through the firewall before troubleshooting requests.

Input validation matters because the domain becomes part of a filename and is interpolated into generated configuration. The example rejects whitespace, shell metacharacters, and relative document-root paths rather than attempting to sanitize them after the fact.

A repeatable, rollback-aware Bash script

Complete script

Save this as provision-nginx-site, make it executable, and review the generated policy for your application before using it in production.

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

if (( EUID != 0 )); then
    echo "Run as root, for example: sudo $0 example.com /srv/www/example.com/public" >&2
    exit 1
fi

if (( $# != 2 )); then
    echo "Usage: $0 DOMAIN DOCUMENT_ROOT" >&2
    exit 2
fi

domain=$1
docroot=$2

# The domain becomes a filename and a server_name value.
if [[ ! "$domain" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$ ]]; then
    echo "Invalid domain: use letters, digits, dots, and hyphens only" >&2
    exit 2
fi

# Require an absolute path and reject characters that can alter shell or config parsing.
if [[ "$docroot" != /* || "$docroot" == *[[:space:]
0;&|`$]* || "$docroot" == *..* ]]; then
    echo "Invalid document root: use a safe absolute path without whitespace or '..'" >&2
    exit 2
fi

available=/etc/nginx/sites-available
enabled=/etc/nginx/sites-enabled
target=$available/$domain
link=$enabled/$domain

mkdir -p -- "$available" "$enabled" "$docroot"

# Refuse to manage a symlink presented as the source file.
if [[ -L "$target" ]]; then
    echo "Refusing to replace symlink: $target" >&2
    exit 1
fi

# An existing enabled entry must point to this exact source file.
if [[ -e "$link" || -L "$link" ]]; then
    if [[ ! -L "$link" ]]; then
        echo "Refusing to overwrite non-symlink: $link" >&2
        exit 1
    fi
    resolved_link=$(readlink -f -- "$link" || true)
    resolved_target=$(readlink -f -- "$target" || true)
    if [[ "$resolved_link" != "$resolved_target" ]]; then
        echo "Refusing to overwrite unrelated link: $link" >&2
        exit 1
    fi
    link_created=0
else
    link_created=1
fi

temp=$(mktemp "$available/.${domain}.tmp.XXXXXX")
cleanup() {
    [[ -z "${temp:-}" ]] || rm -f -- "$temp"
}
trap cleanup EXIT

cat <<NGINX >"$temp"
server {
    listen 80;
    listen [::]:80;
    server_name $domain;
    root $docroot;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
NGINX

had_existing=0
backup=
if [[ -e "$target" ]]; then
    backup="$target.$(date +%Y%m%d%H%M%S).bak"
    cp -a -- "$target" "$backup"
    had_existing=1
fi

# The temporary file is complete before it replaces the managed destination.
mv -f -- "$temp" "$target"
temp=

rollback() {
    if (( link_created )); then
        rm -f -- "$link"
    fi
    if (( had_existing )); then
        cp -a -- "$backup" "$target"
    else
        rm -f -- "$target"
    fi
}

if (( link_created )); then
    ln -s -- "$target" "$link"
fi

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

if ! systemctl reload nginx; then
    echo "The configuration tested successfully, but Nginx could not be reloaded." >&2
    exit 1
fi

echo "Enabled and reloaded $domain"
if (( had_existing )); then
    echo "Backup retained at $backup"
fi

What each phase protects

  1. Strict execution: set -Eeuo pipefail stops on unset variables and failed commands instead of continuing with partial state. The root check prevents a late permission error after files have already been created.
  2. Argument checks: the domain is limited to characters that are safe for the generated filename and server_name. The document root must be absolute and cannot contain whitespace, shell metacharacters, or .. path components.
  3. Directory creation: mkdir -p makes the available directory, enabled directory, and document root safe to repeat. It does not delete existing content.
  4. Symlink inspection: an existing enabled entry is accepted only when it resolves to the target file. A regular file, dangling unrelated link, or link to another source causes an immediate refusal.
  5. Same-filesystem staging: mktemp creates the draft inside sites-available. Moving it into place is therefore a same-filesystem operation, and the destination is not touched until the complete block has been written.
  6. Backup: an existing regular configuration receives a timestamped copy before replacement. Successful runs retain that copy for manual recovery or auditing.
  7. Validation and rollback: the new file is enabled, then the full system configuration is checked. If Nginx reports an error, a newly created link is removed and the old file is copied back; the running worker processes are not reloaded with the invalid file.
  8. Reload: only a successful nginx -t reaches systemctl reload nginx. Reload applies the valid configuration without the stop/start workflow.

Install and run the script

  1. Write the file and restrict execution to administrators:
sudo install -m 0750 provision-nginx-site /usr/local/sbin/provision-nginx-site
  1. Create a site and its public directory:
sudo /usr/local/sbin/provision-nginx-site example.com /srv/www/example.com/public
  1. Place an index.html or application entry point beneath the document root. For a static smoke test, the file can contain a unique title for the site.
  2. Check that the generated file contains the intended name and root:
sudo sed -n '1,120p' /etc/nginx/sites-available/example.com
sudo readlink -f /etc/nginx/sites-enabled/example.com

A successful run prints an enablement and reload message. If the site already exists, the next run creates a new backup and replaces only the file managed by that domain. Running the command again with unchanged arguments should not create duplicate symlinks or additional server blocks.

Adding more than one hostname

The base script deliberately accepts one domain argument so its filename, validation, and rollback behavior remain easy to reason about. If the site must answer for an alias such as www.example.com, edit the generated block’s server_name line to list both names:

server_name example.com www.example.com;

Keep the names separated by spaces and terminate the directive with a semicolon. Both names must resolve to the host before HTTPS issuance. If you extend the script to accept aliases, validate every alias with the same domain rule; do not concatenate an unvalidated argument into the configuration.

For independent applications, use separate server-block files and distinct server_name values. Choose listener ports deliberately when another service already binds the desired address and port. A collision is a service-level problem, not something a symlink can solve.

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

Why nginx -t belongs before every reload

The Nginx test command checks configuration syntax and attempts to open referenced files. It is more useful than checking only the generated site file because the effective configuration also includes the main file, enabled-site links, referenced certificates, and other included files.

The script uses the explicit system configuration path:

sudo nginx -t -c /etc/nginx/nginx.conf

On systems that use the default compiled path, sudo nginx -t is equivalent. Do not substitute a successful text inspection for this command. A file can look correct while an include path is missing or another enabled site contains a syntax error.

When the test fails, Nginx prints the exact file and line it could not parse or open. The correct sequence is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Read the first diagnostic and open the named file at the reported line.
  2. Repair the generated block, an include, a referenced file, or a conflicting directive.
  3. Run the same nginx -t command again.
  4. Reload only after the test reports a successful configuration.

Keep HTTPS issuance separate

Provisioning an HTTP server block and obtaining a certificate are related but different operations. Separating them makes failures observable: a broken ACME challenge cannot be mistaken for a malformed Nginx block.

Install Certbot

The documented Ubuntu setup installs Certbot through Snap:

sudo snap install --classic certbot

The official instructions also show an optional convenience link:

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

Request and install a certificate

After DNS points at the server and port 80 is reachable from the public internet, request the names present in the enabled block:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo certbot --nginx -d example.com -d www.example.com

Certbot’s Nginx plugin finds the matching enabled server block, adds the TLS directives in place, and reloads Nginx. This is why the HTTP block and its names must already be correct before running the command.

Check renewal independently

The packaged Certbot setup includes automated renewal through a cron job or systemd timer. Use the renewal command and its dry-run workflow to verify that the renewal path works, rather than waiting for the first certificate to expire:

sudo certbot renew --dry-run

Keep certificate edits and site provisioning in separate functions or scripts. A failed challenge can then be fixed through DNS, firewall, or reachability changes without rewriting a known-good server block.

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

Repeat-run behavior, backups, and recovery

When a site already exists

The script preserves the existing regular file as example.com.YYYYMMDDHHMMSS.bak before replacing it. The timestamp is generated at execution time, so each replacement has a distinct recovery point when runs occur in different seconds. Review and prune old backups according to your configuration-retention policy; the script does not delete them.

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

When validation fails

The rollback function restores the previous file and removes a symlink that this run created. Because the reload is never attempted after a failed test, the already running Nginx workers continue serving the last successfully loaded configuration. Fix the diagnostic, run the provisioning command again, and validate once more.

When reload fails after a successful test

A successful syntax test means the files can be parsed, but a service reload can still fail because of service state, permissions outside the configuration test, or a system-level problem. Inspect the service status and journal, correct the underlying issue, and rerun the reload. Do not respond by deleting the enabled link; the file has already passed Nginx’s configuration check.

Troubleshooting branches

“Permission denied” while writing or reloading

Invoke the script with sudo or as root. Confirm that the script itself is executable and that the Nginx service account can read the document root. The script’s directory creation does not assign application ownership, because the correct owner depends on whether the root contains static files, an upload area, or files generated by another service.

The browser returns the wrong site

Inspect the enabled links and every matching server_name. Confirm that the request’s hostname is exactly one of the names in the intended block and that DNS resolves to this server. An old enabled block or the Ubuntu default site may still answer a name that has not been assigned uniquely.

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

Nginx reports a missing file

Use the path printed by nginx -t. Missing certificate files, included snippets, and document-root references must exist with permissions that allow Nginx to traverse their parent directories. Correct the path or install the missing file, test again, and only then reload.

The generated site returns 404

The sample block uses try_files $uri $uri/ =404;, so a request succeeds only when the requested file or directory exists beneath the configured root. Confirm that the requested file is under /srv/www/example.com/public (or your chosen root), that the name is case-correct, and that the worker account can read it.

Port 80 cannot be reached

Verify that Nginx is listening on the address and port in the block, that no other service owns the port, and that firewall rules permit the connection. DNS correctness alone does not open a network port.

Certbot cannot complete the challenge

First verify the enabled HTTP block with nginx -t, then check that every requested domain resolves to this host and that port 80 is reachable externally. Keep the HTTP provisioning script unchanged while diagnosing the ACME challenge; certificate issuance is a separate stage.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Operational checklist

  • Use a deterministic filename under /etc/nginx/sites-available.
  • Validate the domain and document-root arguments before interpolation.
  • Write the draft to a temporary file in the same filesystem.
  • Refuse to overwrite an unrelated enabled link or a source-file symlink.
  • Back up an existing configuration before replacement.
  • Create the enabled symlink only when it is absent, and verify an existing one resolves to the intended file.
  • Run nginx -t against the complete system configuration.
  • Rollback the file and any newly created link when validation fails.
  • Reload with systemctl reload nginx only after a successful test.
  • Check document-root readability, DNS, firewall rules, and upstream application health independently.
  • Run Certbot only after the HTTP site is enabled and reachable.
  • Test automated certificate renewal with a dry run.

Manual commands versus the guarded script

Operation Manual approach Guarded automation
Create a site file Write directly under sites-available. Build a complete temporary file, then move it into place.
Enable a site Run ln -s manually. Verify an existing link or create one without overwriting an unrelated entry.
Handle repeat runs Depends on the operator’s checks. Uses deterministic paths, idempotent directory creation, and timestamped backups.
Validate changes Operator may reload immediately. Runs nginx -t and stops on failure.
Recover from a bad edit Restore a manually chosen copy. Copies the timestamped backup back and removes a newly created link after a failed test.
HTTPS Certificate editing is mixed into site setup. HTTP provisioning is completed and tested before Certbot modifies the block.

Extending the pattern safely

For a larger fleet, keep the same boundaries even if you add templates, aliases, application upstreams, or deployment metadata. Validate every value before rendering it, stage each file before replacement, preserve a rollback copy, and make the final test and reload visible in logs. If a deployment system runs the script concurrently, add an external lock so two processes cannot back up and replace the same domain simultaneously.

Do not make removal implicit. A separate, explicitly requested command can disable a site by checking that its link points to the expected source and then removing only that link. Likewise, leave deletion of sites-enabled/default as an operator decision rather than hiding it in a provisioning routine.

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 quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

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.