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.