Docs / Automation & IaC / How to Write Bash Scripts for Server Automation

How to Write Bash Scripts for Server Automation

By Admin · Mar 2, 2026 · Updated Apr 23, 2026 · 27 views · 3 min read

How to Write Bash Scripts for Server Automation

Bash scripts are the foundation of server automation on Linux. Whether you are setting up a new Breeze instance or automating routine maintenance, well-written Bash scripts save time and eliminate human error.

Script Structure and Best Practices

Start every script with a proper shebang, strict mode, and header comments:

#!/usr/bin/env bash
#
# setup-webserver.sh - Automate Nginx installation and configuration
# Usage: sudo ./setup-webserver.sh [domain]
#

set -euo pipefail
IFS=$'\n\t'

# Exit codes
readonly E_SUCCESS=0
readonly E_MISSING_ARG=1
readonly E_NOT_ROOT=2

# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'

log_info()  { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }

Input Validation and Error Handling

Always validate inputs and check prerequisites:

# Check if running as root
if [[ $EUID -ne 0 ]]; then
    log_error "This script must be run as root"
    exit $E_NOT_ROOT
fi

# Validate arguments
DOMAIN="${1:-}"
if [[ -z "$DOMAIN" ]]; then
    log_error "Usage: $0 <domain>"
    exit $E_MISSING_ARG
fi

# Check for required commands
for cmd in nginx openssl curl; do
    if ! command -v "$cmd" &> /dev/null; then
        log_warn "$cmd not found, installing..."
        apt-get install -y "$cmd"
    fi
done

Functions for Modularity

Organize your script into functions for readability and reuse:

install_packages() {
    log_info "Installing required packages..."
    apt-get update -y
    apt-get install -y nginx ufw fail2ban curl wget
}

configure_firewall() {
    log_info "Configuring UFW firewall..."
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow 22/tcp comment "SSH"
    ufw allow 80/tcp comment "HTTP"
    ufw allow 443/tcp comment "HTTPS"
    ufw --force enable
    log_info "Firewall configured successfully"
}

configure_nginx() {
    local domain="$1"
    log_info "Configuring Nginx for ${domain}..."

    cat > "/etc/nginx/sites-available/${domain}" <<NGINX
server {
    listen 80;
    server_name ${domain} www.${domain};
    root /var/www/${domain}/html;
    index index.html;

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

    ln -sf "/etc/nginx/sites-available/${domain}" "/etc/nginx/sites-enabled/"
    rm -f /etc/nginx/sites-enabled/default
    nginx -t && systemctl reload nginx
}

setup_directories() {
    local domain="$1"
    mkdir -p "/var/www/${domain}/html"
    echo "<h1>Welcome to ${domain}</h1>" > "/var/www/${domain}/html/index.html"
    chown -R www-data:www-data "/var/www/${domain}"
}

Trap for Cleanup

Use trap to clean up temporary files and handle interruptions:

TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"; log_info "Cleanup complete"' EXIT

# Use the temp file safely
curl -sS "https://example.com/config" -o "$TMPFILE"
cp "$TMPFILE" /etc/myapp/config.conf

Putting It All Together

# Main execution
main() {
    log_info "Starting server setup for ${DOMAIN}"
    install_packages
    configure_firewall
    setup_directories "$DOMAIN"
    configure_nginx "$DOMAIN"
    log_info "Setup complete! Visit http://${DOMAIN}"
}

main

Key Bash Scripting Rules

  • Always use set -euo pipefail — exit on errors, undefined variables, and pipe failures
  • Quote all variables — prevent word splitting and globbing surprises
  • Use functions — keep logic modular and testable
  • Log everything — print status messages so you can trace what happened
  • Use [[ ]] over [ ] — safer conditionals with pattern matching support
  • Use shellcheck — lint your scripts to catch common mistakes
  • Make scripts idempotent — safe to run multiple times without side effects

Solid Bash scripting skills let you automate virtually anything on your Breeze instances, from initial provisioning to ongoing maintenance.

Was this article helpful?