A provisioning script configures a fresh server from bare Ubuntu to a fully configured application host — installing packages, creating users, setting up firewall rules, deploying configs, and registering services. It's Infrastructure as Code without the framework overhead.
1Complete server provisioning script
BASH
#!/usr/bin/env bash
# provision.sh — Provision a fresh Ubuntu server for myapp
set -euo pipefail
[[ "$(id -u)" -eq 0 ]] || { echo "Must run as root"; exit 1; }
LOG="/var/log/provision.log"
APP_USER="deploy"
APP_DIR="/opt/myapp"
DOMAIN="${1:?Usage: provision.sh DOMAIN}"
SLACK_URL="${SLACK_WEBHOOK_URL:-}"
log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "${LOG}"; }
step(){ log ""; log "▶ $*"; }
log "=== Provisioning: $(hostname) — $(date) ==="
log "Domain: ${DOMAIN}"
# ── 1. System update ──────────────────────────────────────
step "System update"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get upgrade -y -qq
log " System updated"
# ── 2. Required packages ──────────────────────────────────
step "Installing packages"
apt-get install -y -qq \
nginx php8.3-fpm php8.3-mysql php8.3-redis php8.3-gd php8.3-mbstring \
mysql-client redis-tools \
git curl wget rsync htop iotop \
certbot python3-certbot-nginx \
fail2ban ufw \
jq bc netcat-openbsd
log " Packages installed"
# ── 3. Create application user ────────────────────────────
step "Creating application user: ${APP_USER}"
if ! id "${APP_USER}" &>/dev/null; then
useradd -m -s /bin/bash -G www-data "${APP_USER}"
install -d -m 700 "/home/${APP_USER}/.ssh"
# Copy authorised keys from root
cp /root/.ssh/authorized_keys "/home/${APP_USER}/.ssh/" 2>/dev/null || true
chown -R "${APP_USER}:${APP_USER}" "/home/${APP_USER}/.ssh"
fi
log " User ${APP_USER} ready"
# ── 4. Directory structure ────────────────────────────────
step "Setting up directories"
install -d -o "${APP_USER}" -g "${APP_USER}" -m 755 \
"${APP_DIR}"/{releases,shared/{logs,uploads,config}} \
/backups /var/log/myapp
log " Directory structure ready"
# ── 5. Firewall ───────────────────────────────────────────
step "Configuring firewall"
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 'Nginx Full'
ufw --force enable
log " Firewall: SSH + HTTP/HTTPS only"
# ── 6. Nginx configuration ────────────────────────────────
step "Configuring Nginx"
cat > "/etc/nginx/sites-available/${DOMAIN}" << NGINXCONF
server {
listen 80;
server_name ${DOMAIN};
root ${APP_DIR}/current/public;
index index.php;
access_log /var/log/nginx/${DOMAIN}_access.log;
error_log /var/log/nginx/${DOMAIN}_error.log;
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
}
location ~ /\.ht { deny all; }
}
NGINXCONF
ln -sfn "/etc/nginx/sites-available/${DOMAIN}" /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
log " Nginx configured for ${DOMAIN}"
# ── 7. SSL certificate ────────────────────────────────────
step "Obtaining SSL certificate"
certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos \
--email "ops@example.com" --redirect
log " SSL certificate obtained"
# ── 8. Configure sudoers for deploy user ─────────────────
step "Configuring sudoers"
cat > /etc/sudoers.d/deploy << SUDOCONF
${APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx
${APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl reload php8.3-fpm
${APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp-worker
SUDOCONF
chmod 440 /etc/sudoers.d/deploy
log " Sudoers configured"
# ── 9. Schedule jobs ──────────────────────────────────────
step "Installing cron jobs"
cat > /etc/cron.d/myapp << CRONTAB
# Backups: 2am daily
0 2 * * * ${APP_USER} /opt/scripts/backup_orchestrator.sh >> /var/log/backup.log 2>&1
# Cert renewal: 3am daily
0 3 * * * root certbot renew --quiet --deploy-hook 'nginx -s reload'
# Health monitor: every 5 minutes
*/5 * * * * ${APP_USER} /opt/scripts/monitor.sh 2>&1 | logger -t monitor
CRONTAB
log " Cron jobs installed"
log ""
log "=== Provisioning complete: $(date) ==="
log "Next steps:"
log " 1. Copy .env to ${APP_DIR}/shared/.env"
log " 2. Run: sudo -u ${APP_USER} ./deploy.sh main"
[[ -n "${SLACK_URL}" ]] && curl -s -X POST "${SLACK_URL}" \
-d "{\"text\":\"✅ Server provisioned: $(hostname) for ${DOMAIN}\"}" &>/dev/null
✔ Provisioning rules — Always run as root with
set -euo pipefail so the first error aborts and doesn't leave the server in a half-provisioned state. Log every step to a file — you'll need it for debugging. Set the firewall before anything external-facing is installed. Use DEBIAN_FRONTEND=noninteractive to prevent apt prompts from blocking the script. Test provisioning by running on a fresh cloud instance before running on production.