Complete OpenStack Installation on DigitalOcean with LEMP Stack
This guide documents the complete process of deploying OpenStack (via MicroStack) on a DigitalOcean droplet running Ubuntu 24.04 LTS, with a production-hardened LEMP stack serving as the front-end web infrastructure. It covers everything from initial server provisioning through launching your first VM, including all troubleshooting steps, bug fixes, and architectural decisions made along the way. Whether you are building a private cloud for development, testing, or production evaluation, this guide serves as a single source of truth.
1. Introduction and Overview
1.1 What is OpenStack?
OpenStack is an open-source cloud computing platform that provides infrastructure-as-a-service (IaaS) for provisioning and managing virtual machines, networks, block storage, and object storage. Originally developed by NASA and Rackspace in 2010, it has grown into one of the most widely deployed private cloud platforms, backed by a global community of developers and organizations.
OpenStack consists of multiple core services, each responsible for a different aspect of cloud infrastructure:
- Keystone — Identity and authentication service
- Nova — Compute service for managing VM instances
- Neutron — Networking service for defining networks, subnets, and routers
- Glance — Image service for storing and retrieving VM disk images
- Cinder — Block storage service for persistent volumes
- Horizon — Web-based dashboard for managing OpenStack resources
- Swift — Object storage service (optional, not used in this deployment)
1.2 Why Use OpenStack?
OpenStack gives you the power of a public cloud on infrastructure you control. Key advantages include:
- No vendor lock-in — Fully open source with a massive ecosystem
- Cost control — Run multiple VMs on a single droplet, optimizing resource usage
- API-driven automation — Every operation is scriptable via REST APIs or CLI
- Full network control — Define custom topologies with routers, subnets, and floating IPs
- Learning platform — Ideal for DevOps engineers studying cloud infrastructure
1.3 Architecture Overview
This deployment uses an all-in-one topology where the LEMP stack and OpenStack services coexist on a single DigitalOcean droplet. The following ASCII diagram illustrates the architecture:
+-------------------------------------------------------------+
| DigitalOcean Droplet |
| 4 vCPU / 8 GB RAM / 160 GB SSD |
| Ubuntu 24.04 LTS |
| |
| +------------------+ +---------------------------+ |
| | LEMP Stack | | OpenStack | |
| | | | (MicroStack) | |
| | Nginx 1.24.0 |<---->| Horizon Dashboard | |
| | (Reverse Proxy) | | http://10.20.20.1 | |
| | | | | |
| | MySQL 8.0 | | Keystone (Auth) | |
| | (App DB) | | Nova (Compute) | |
| | | | Neutron (Networking) | |
| | PHP 8.3-FPM | | Glance (Images) | |
| | | | Cinder (Block Storage) | |
| +------------------+ +---------------------------+ |
| | | |
| +--------v------------------v-----------+ |
| | UFW Firewall + Fail2ban | |
| +---------------------------------------+ |
| | |
+-----------v--------------------------------------------------+
|
openstack.kuyaops.com (A Record)
HTTPS (Let's Encrypt SSL)
1.4 Prerequisites
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| vCPU | 2 | 4+ | OpenStack services are CPU-intensive; 4 vCPU for all-in-one |
| RAM | 4 GB | 8 GB+ | 8 GB is the practical minimum for all-in-one; 4 GB swap required |
| Disk | 80 GB | 160 GB+ | SSD required; images and VMs consume significant space |
| Operating System | Ubuntu 24.04 LTS (Noble Numbat) | ||
| Domain Name | A registered domain with DNS A record pointing to droplet IP | ||
| Nested Virtualization | KVM preferred; QEMU fallback acceptable for light workloads | ||
Warning: Do not attempt this installation on a droplet with less than 8 GB RAM. OpenStack services will fail to start or encounter random OOM kills. If your budget is limited, create a 4 GB swap file to supplement physical RAM (see Section 2.3).
2. Server Preparation
2.1 DigitalOcean Droplet Sizing
For an all-in-one OpenStack deployment, the droplet size directly determines how many concurrent VMs you can run and how responsive the control plane will be. DigitalOcean offers several suitable tiers:
| Droplet Size | vCPU | RAM | Disk | Monthly Cost | Suitability |
|---|---|---|---|---|---|
| Basic Regular 4GB | 2 | 4 GB | 80 GB | $24 | Too small; control plane alone consumes ~3 GB |
| Basic Regular 8GB | 4 | 8 GB | 160 GB | $48 | Minimum for all-in-one (this guide) |
| Basic Regular 16GB | 8 | 16 GB | 320 GB | $96 | Comfortable; can run 4-6 small VMs concurrently |
| CPU-Optimized 16GB | 8 | 16 GB | 200 GB | $126 | Best for CPU-heavy VM workloads |
2.2 Ubuntu 24.04 Initial Setup
After creating the droplet, perform these initial configuration steps:
2.2.1 Update the System
# Update package lists and upgrade all packages
sudo apt update && sudo apt full-upgrade -y
# Reboot if the kernel was updated
sudo reboot
2.2.2 Create a Non-Root User (Optional but Recommended)
# Create a new user and add to sudo group
sudo adduser devops
sudo usermod -aG sudo devops
# Switch to the new user
su - devops
2.2.3 Set the Hostname
# Set the hostname to match your domain
sudo hostnamectl set-hostname openstack.kuyaops.com
# Add to /etc/hosts
echo "$(hostname -I | awk '{print $1}') openstack.kuyaops.com openstack" | sudo tee -a /etc/hosts
2.3 Swap Configuration
OpenStack services, especially when running on a single node, consume substantial RAM. A 4 GB swap file prevents OOM (Out of Memory) kills during installation and normal operation. This is especially critical during the MicroStack initialization phase, when multiple services start simultaneously.
2.3.1 Check Current Swap Status
# Check if swap is already active
sudo swapon --show
free -h
2.3.2 Create a 4 GB Swap File
# Create a 4 GB swap file using fallocate (faster than dd)
sudo fallocate -l 4G /swapfile
# Set secure permissions
sudo chmod 600 /swapfile
# Format as swap
sudo mkswap /swapfile
# Enable the swap file
sudo swapon /swapfile
2.3.3 Make Swap Persistent
# Add to /etc/fstab so swap survives reboots
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
2.3.4 Tune Swappiness
The default vm.swappiness value of 60 is too aggressive for server workloads.
A value of 10 keeps the system from swapping unless absolutely necessary:
# Set swappiness to 10 (applied immediately)
sudo sysctl vm.swappiness=10
# Make persistent across reboots
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
Note: While swap prevents OOM kills, it is not a substitute for physical RAM.
Heavy swapping causes performance degradation. Monitor swap usage with vmstat 1 or
free -h and upgrade your droplet if swap usage is consistently high.
2.4 DNS Configuration
Your domain must have an A record pointing to the droplet's public IP address before running the LEMP installation script (Certbot requires it for Let's Encrypt validation).
2.4.1 Configure A Record
Log in to your domain registrar or DNS provider and create the following record:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | openstack | <your-droplet-public-ip> | 3600 |
2.4.2 Verify DNS Propagation
# Check if the A record resolves correctly
dig +short openstack.kuyaops.com
nslookup openstack.kuyaops.com
# Verify from external DNS servers
dig @8.8.8.8 +short openstack.kuyaops.com
Warning: Do not proceed with the LEMP installation until DNS has fully propagated. Certbot's HTTP-01 challenge will fail if the domain does not resolve to your droplet. DNS propagation can take anywhere from a few minutes to 48 hours depending on your provider and TTL settings.
2.5 Verification Checklist
Before proceeding to Phase 1 (LEMP), confirm the following:
# Check Ubuntu version
lsb_release -a
# Expected: Ubuntu 24.04 LTS (Noble Numbat)
# Check available RAM
free -h
# Expected: Total RAM >= 8 GB, Swap >= 4 GB
# Check available disk space
df -h /
# Expected: At least 40 GB free (160 GB total recommended)
# Check CPU cores
nproc
# Expected: 4 or more
# Verify DNS resolution
ping -c 1 openstack.kuyaops.com
# Expected: Resolves to your droplet's public IP
# Verify internet connectivity
ping -c 3 1.1.1.1
# Expected: 0% packet loss
3. LEMP Stack Installation
3.1 What is LEMP?
LEMP is an acronym for a popular web service stack composed of four open-source components:
- Linux — The operating system (Ubuntu 24.04 LTS)
- Nginx (pronounced "Engine-X") — High-performance web server and reverse proxy
- MySQL — Relational database management system
- PHP — Server-side scripting language with FastCGI Process Manager (FPM)
In this deployment, Nginx serves as the reverse proxy for the OpenStack Horizon dashboard, while MySQL and PHP provide a foundation for any additional web applications you may deploy on the same droplet.
3.2 Script Overview: setup_lemp_improved.sh
Rather than installing each component manually, a single bash script automates the entire LEMP stack installation. The script is designed to be idempotent (safe to run multiple times) and includes extensive error handling, retry logic, and troubleshooting helpers.
3.2.1 Key Features
| Feature | Description | Benefit |
|---|---|---|
| Dpkg lock contention handler | Waits for apt locks with spinner; auto-clears stale locks; kills stuck processes after 5 min | Eliminates "Could not get lock" failures during unattended installs |
| Exponential backoff retry | 3 retries at 5s, 15s, and 45s delays for apt operations | Recovers from transient network failures |
| Service verification | Polls systemctl for up to 15s (30s for MySQL); shows journalctl on failure | Immediate feedback when services fail to start |
| Idempotency | DROP IF EXISTS + CREATE for DB; config comparison before write; UFW deduplication | Safe to re-run without side effects |
| Credential persistence | Saves credentials to /root/.lemp_credentials (chmod 600); re-reads on re-run | No credential loss between runs |
| Pre-flight backups | Timestamped backups in /root/lemp-backups/YYYY-MM-DD-HHMMSS/ | Rollback capability before making changes |
| PHP version cleanup | Detects and disables old PHP-FPM when PHP_VERSION changes | Prevents conflicting PHP-FPM sockets |
| RAM check + auto swap | Creates 4 GB swap if RAM < 2 GB and no swap active | Prevents OOM kills on smaller droplets |
| MySQL broken-state recovery | Detects iU (installed but unconfigured) packages; purges and reinstalls | Recovers from interrupted MySQL installations |
| Port availability check | Warns if ports 80/443 are already bound before installing | Prevents Nginx startup failures |
3.3 Configuration Variables
Edit these variables at the top of the script before running. They control the domain, SSL certificate email, database credentials, and PHP version.
# ==============================================
# CONFIGURATION VARIABLES
# ==============================================
DOMAIN="openstack.kuyaops.com"
EMAIL="admin@kuyaops.com" # For Let's Encrypt SSL
DB_NAME="openstack_db"
DB_USER="openstack_user"
PHP_VERSION="8.3" # PHP 8.3 is the latest stable
# Auto-generated if not already present:
# DB_PASSWORD — random 24-character string
# MYSQL_ROOT_PASSWORD — random 24-character string
# Generated credentials are saved to:
# /root/.lemp_credentials (chmod 600)
3.4 Complete LEMP Installation Script
Save the following script as /root/setup_lemp_improved.sh:
#!/bin/bash
# =============================================================================
# LEMP Stack Installation Script for Ubuntu 24.04 LTS
# Version: 2.0 (Production-Ready with Full Error Handling)
# Description: Installs Nginx, MySQL, PHP-FPM with SSL, firewall, and security
# =============================================================================
set -euo pipefail
# -----------------------------------------------
# CONFIGURATION VARIABLES
# -----------------------------------------------
DOMAIN="openstack.kuyaops.com"
EMAIL="admin@kuyaops.com"
DB_NAME="openstack_db"
DB_USER="openstack_user"
PHP_VERSION="8.3"
CREDENTIALS_FILE="/root/.lemp_credentials"
BACKUP_DIR="/root/lemp-backups/$(date +%Y-%m-%d-%H%M%S)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# -----------------------------------------------
# UTILITY FUNCTIONS
# -----------------------------------------------
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_err() { echo -e "${RED}[ERROR]${NC} $1"; }
spinner() {
local pid=$1 delay=0.1 spinstr='|/-\'
while kill -0 "$pid" 2>/dev/null; do
local temp=${spinstr#?}
printf " [%c] " "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep $delay
printf "\b\b\b\b\b\b"
done
printf " \b\b\b\b"
}
wait_for_apt_locks() {
local timeout=300 elapsed=0
log_info "Checking for apt lock contention..."
while elapsed < timeout; do
if ! lsof /var/lib/dpkg/lock-frontend &>/dev/null && \
! lsof /var/lib/apt/lists/lock &>/dev/null && \
! lsof /var/cache/apt/archives/lock &>/dev/null; then
log_ok "No apt locks detected"
return 0
fi
# Find and display the process holding the lock
local lock_pid=$(lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null | head -1)
if [ -n "$lock_pid" ]; then
log_warn "PID $lock_pid holding dpkg lock: $(ps -p $lock_pid -o comm= 2>/dev/null || echo 'unknown')"
fi
sleep 5
elapsed=$((elapsed + 5))
done
# Timeout reached: attempt recovery
log_err "Apt locks held for over 5 minutes. Attempting recovery..."
rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock
dpkg --configure -a 2>/dev/null || true
apt-get update || true
}
apt_with_retry() {
local retries=3 delay=5
for i in $(seq 1 $retries); do
if apt-get "$@"; then
return 0
fi
log_warn "apt attempt $i/$retries failed. Retrying in ${delay}s..."
sleep $delay
delay=$((delay * 3))
wait_for_apt_locks
done
log_err "apt failed after $retries attempts"
return 1
}
verify_service() {
local service=$1 max_wait=${2:-15}
log_info "Verifying $service is running..."
for i in $(seq 1 $max_wait); do
if systemctl is-active --quiet "$service" 2>/dev/null; then
log_ok "$service is running"
return 0
fi
sleep 1
done
log_err "$service failed to start within ${max_wait}s"
echo "--- Recent journal entries for $service ---"
journalctl -u "$service" --no-pager -n 20 || true
return 1
}
generate_password() {
openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 24
}
# -----------------------------------------------
# PRE-FLIGHT CHECKS
# -----------------------------------------------
pre_flight_checks() {
log_info "Running pre-flight checks..."
# Check root
if [ "$EUID" -ne 0 ]; then
log_err "This script must be run as root"
exit 1
fi
# Check OS
if ! lsb_release -a 2>/dev/null | grep -q "Ubuntu 24.04"; then
log_warn "This script is designed for Ubuntu 24.04 LTS"
fi
# Check RAM and create swap if needed
local total_ram=$(free -m | awk '/^Mem:/{print $2}')
local total_swap=$(free -m | awk '/^Swap:/{print $2}')
log_info "RAM: ${total_ram}MB, Swap: ${total_swap}MB"
if [ "$total_ram" -lt 2048 ] && [ "$total_swap" -eq 0 ]; then
log_warn "Low RAM detected (${total_ram}MB) and no swap. Creating 4GB swap..."
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
sysctl vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.conf
log_ok "4GB swap created and activated"
fi
# Check disk space
local free_disk=$(df -m / | awk 'NR==2{print $4}')
if [ "$free_disk" -lt 10240 ]; then
log_err "Insufficient disk space: ${free_disk}MB free (10GB minimum required)"
exit 1
fi
log_ok "Disk space OK: ${free_disk}MB free"
# Check for port conflicts
if ss -tlnp | grep -q ':80 '; then
log_warn "Port 80 is already in use: $(ss -tlnp | grep ':80 ')"
fi
if ss -tlnp | grep -q ':443 '; then
log_warn "Port 443 is already in use: $(ss -tlnp | grep ':443 ')"
fi
# Create backup directory
mkdir -p "$BACKUP_DIR"
log_ok "Backup directory: $BACKUP_DIR"
}
# -----------------------------------------------
# BACKUP EXISTING CONFIGURATION
# -----------------------------------------------
backup_existing() {
log_info "Backing up existing configuration..."
[ -d /etc/nginx ] && cp -r /etc/nginx "$BACKUP_DIR/" 2>/dev/null || true
[ -f /etc/mysql/my.cnf ] && cp /etc/mysql/my.cnf "$BACKUP_DIR/" 2>/dev/null || true
[ -d /etc/php ] && cp -r /etc/php "$BACKUP_DIR/" 2>/dev/null || true
[ -f /etc/ufw/before.rules ] && cp /etc/ufw/before.rules "$BACKUP_DIR/" 2>/dev/null || true
log_ok "Backup completed"
}
# -----------------------------------------------
# LOAD OR GENERATE CREDENTIALS
# -----------------------------------------------
load_credentials() {
if [ -f "$CREDENTIALS_FILE" ]; then
log_info "Loading existing credentials from $CREDENTIALS_FILE"
source "$CREDENTIALS_FILE"
else
DB_PASSWORD=$(generate_password)
MYSQL_ROOT_PASSWORD=$(generate_password)
cat > "$CREDENTIALS_FILE" << EOF
# LEMP Stack Credentials
# Generated: $(date)
# DO NOT COMMIT THIS FILE TO VERSION CONTROL
DB_NAME=$DB_NAME
DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD
MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
DOMAIN=$DOMAIN
PHP_VERSION=$PHP_VERSION
EOF
chmod 600 "$CREDENTIALS_FILE"
log_ok "New credentials generated and saved to $CREDENTIALS_FILE"
fi
}
# -----------------------------------------------
# FIX BROKEN MYSQL STATE
# -----------------------------------------------
fix_broken_mysql() {
log_info "Checking for broken MySQL installation..."
local mysql_state=$(dpkg -l mysql-server 2>/dev/null | awk 'NR==6{print $1}' || echo "")
if [ "$mysql_state" = "iU" ]; then
log_warn "MySQL in 'installed but unconfigured' (iU) state. Purging and reinstalling..."
dpkg --configure -a || true
apt-get purge -y mysql-server mysql-client mysql-common || true
apt-get autoremove -y || true
rm -rf /var/lib/mysql /etc/mysql
log_ok "Broken MySQL state cleaned up"
fi
}
# -----------------------------------------------
# INSTALL NGINX
# -----------------------------------------------
install_nginx() {
log_info "Installing Nginx..."
apt_with_retry update
apt_with_retry install -y nginx
systemctl enable nginx
verify_service nginx
log_ok "Nginx $(nginx -v 2>&1 | awk -F'/' '{print $2}') installed"
}
# -----------------------------------------------
# INSTALL AND SECURE MYSQL
# -----------------------------------------------
install_mysql() {
log_info "Installing MySQL 8.0..."
fix_broken_mysql
apt_with_retry install -y mysql-server mysql-client
# Wait for MySQL to be ready (up to 30s)
verify_service mysql 30
# Secure MySQL installation
log_info "Securing MySQL..."
mysql -uroot << EOF
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '$MYSQL_ROOT_PASSWORD';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
EOF
# Create application database and user
mysql -uroot -p"$MYSQL_ROOT_PASSWORD" << EOF
DROP DATABASE IF EXISTS $DB_NAME;
CREATE DATABASE $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP USER IF EXISTS '$DB_USER'@'localhost';
CREATE USER '$DB_USER'@'localhost' IDENTIFIED WITH caching_sha2_password BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOF
# Enable auth_socket for root (safer local access)
mysql -uroot -p"$MYSQL_ROOT_PASSWORD" << 'EOF'
ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;
FLUSH PRIVILEGES;
EOF
log_ok "MySQL 8.0 installed and secured"
}
# -----------------------------------------------
# INSTALL PHP-FPM
# -----------------------------------------------
install_php() {
log_info "Installing PHP $PHP_VERSION-FPM..."
# Clean up old PHP versions if PHP_VERSION changed
local current_php=$(php -v 2>/dev/null | head -1 | grep -oP '\d+\.\d+' || echo "")
if [ -n "$current_php" ] && [ "$current_php" != "$PHP_VERSION" ]; then
log_warn "Detected PHP $current_php, disabling for upgrade to $PHP_VERSION..."
systemctl stop "php${current_php}-fpm" 2>/dev/null || true
systemctl disable "php${current_php}-fpm" 2>/dev/null || true
fi
apt_with_retry install -y \
"php${PHP_VERSION}-fpm" \
"php${PHP_VERSION}-mysql" \
"php${PHP_VERSION}-curl" \
"php${PHP_VERSION}-gd" \
"php${PHP_VERSION}-mbstring" \
"php${PHP_VERSION}-xml" \
"php${PHP_VERSION}-zip" \
"php${PHP_VERSION}-intl" \
"php${PHP_VERSION}-opcache" \
"php${PHP_VERSION}-bcmath"
# Harden php.ini
local php_ini="/etc/php/${PHP_VERSION}/fpm/php.ini"
cp "$php_ini" "$BACKUP_DIR/php.ini.bak"
sed -i 's/^;*cgi.fix_pathinfo=.*/cgi.fix_pathinfo=0/' "$php_ini"
sed -i 's/^;*upload_max_filesize.*/upload_max_filesize = 64M/' "$php_ini"
sed -i 's/^;*post_max_size.*/post_max_size = 64M/' "$php_ini"
sed -i 's/^;*memory_limit.*/memory_limit = 256M/' "$php_ini"
sed -i 's/^;*max_execution_time.*/max_execution_time = 300/' "$php_ini"
# Tune OPcache for production
local opcache_ini="/etc/php/${PHP_VERSION}/fpm/conf.d/10-opcache.ini"
cat >> "$opcache_ini" << 'EOF'
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.validate_timestamps=0
EOF
systemctl restart "php${PHP_VERSION}-fpm"
verify_service "php${PHP_VERSION}-fpm"
log_ok "PHP $PHP_VERSION-FPM installed and tuned"
}
# -----------------------------------------------
# CONFIGURE NGINX VIRTUAL HOST
# -----------------------------------------------
configure_nginx() {
log_info "Configuring Nginx for $DOMAIN..."
local nginx_conf="/etc/nginx/sites-available/${DOMAIN}"
local nginx_enabled="/etc/nginx/sites-enabled/${DOMAIN}"
cat > "$nginx_conf" << EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN;
root /var/www/$DOMAIN;
index index.php index.html;
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php${PHP_VERSION}-fpm.sock;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
EOF
mkdir -p "/var/www/$DOMAIN"
chown -R www-data:www-data "/var/www/$DOMAIN"
chmod 755 "/var/www/$DOMAIN"
# Create test PHP file
cat > "/var/www/$DOMAIN/index.php" << 'EOF'
<?php phpinfo(); ?>
EOF
# Enable site
rm -f /etc/nginx/sites-enabled/default
ln -sf "$nginx_conf" "$nginx_enabled"
nginx -t && systemctl reload nginx
log_ok "Nginx configured for $DOMAIN"
}
# -----------------------------------------------
# CONFIGURE SSL WITH CERTBOT
# -----------------------------------------------
configure_ssl() {
log_info "Installing Certbot and configuring SSL..."
apt_with_retry install -y certbot python3-certbot-nginx
if ! certbot certificates 2>/dev/null | grep -q "$DOMAIN"; then
certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos -m "$EMAIL" --redirect
log_ok "SSL certificate obtained for $DOMAIN"
else
log_info "SSL certificate for $DOMAIN already exists"
fi
# Auto-renewal test
certbot renew --dry-run
log_ok "SSL auto-renewal verified"
}
# -----------------------------------------------
# CONFIGURE FIREWALL (UFW)
# -----------------------------------------------
configure_firewall() {
log_info "Configuring UFW firewall..."
apt_with_retry install -y ufw
# Reset to known state (idempotent)
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
# Allow SSH (critical - do not lock yourself out)
ufw allow 22/tcp
# Allow Nginx (HTTP + HTTPS)
ufw allow 'Nginx Full'
# Enable firewall
ufw --force enable
log_ok "UFW configured: SSH + Nginx Full allowed"
ufw status verbose
}
# -----------------------------------------------
# CONFIGURE FAIL2BAN
# -----------------------------------------------
configure_fail2ban() {
log_info "Installing and configuring Fail2ban..."
apt_with_retry install -y fail2ban
# Main configuration
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
[nginx-badbots]
enabled = true
filter = nginx-badbots
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
[nginx-noscript]
enabled = true
filter = nginx-noscript
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 6
EOF
systemctl enable fail2ban
systemctl restart fail2ban
verify_service fail2ban
log_ok "Fail2ban configured"
fail2ban-client status
}
# -----------------------------------------------
# PRINT SUMMARY
# -----------------------------------------------
print_summary() {
echo ""
echo "============================================="
echo " LEMP STACK INSTALLATION COMPLETE"
echo "============================================="
echo "Domain: https://$DOMAIN"
echo "Web Root: /var/www/$DOMAIN"
echo "Database: $DB_NAME"
echo "DB User: $DB_USER"
echo "PHP Version: $PHP_VERSION"
echo "Credentials: $CREDENTIALS_FILE"
echo "Backups: $BACKUP_DIR"
echo "============================================="
echo ""
echo "Services:"
systemctl is-active nginx &>/dev/null && echo " [OK] Nginx" || echo " [FAIL] Nginx"
systemctl is-active mysql &>/dev/null && echo " [OK] MySQL" || echo " [FAIL] MySQL"
systemctl is-active "php${PHP_VERSION}-fpm" &>/dev/null && echo " [OK] PHP-FPM" || echo " [FAIL] PHP-FPM"
systemctl is-active fail2ban &>/dev/null && echo " [OK] Fail2ban" || echo " [FAIL] Fail2ban"
echo "============================================="
}
# -----------------------------------------------
# MAIN EXECUTION
# -----------------------------------------------
main() {
log_info "Starting LEMP stack installation..."
pre_flight_checks
backup_existing
load_credentials
install_nginx
install_mysql
install_php
configure_nginx
configure_ssl
configure_firewall
configure_fail2ban
print_summary
log_ok "Installation complete!"
}
main "$@"
3.5 Running the Script
3.5.1 Basic Execution
# Download and run the script as root
curl -o /root/setup_lemp_improved.sh https://your-repo/setup_lemp_improved.sh
chmod +x /root/setup_lemp_improved.sh
sudo /root/setup_lemp_improved.sh
3.5.2 Dry-Run Mode (Simulate Without Making Changes)
For a dry run, wrap the script in a function or use the --dry-run flag if implemented.
Alternatively, review the pre-flight checks section which safely reports system state without
modifying anything:
# The script's pre_flight_checks() function reads-only and is safe to run
# It reports: RAM, disk, ports in use, and apt lock status
bash -c 'source /root/setup_lemp_improved.sh && pre_flight_checks'
3.5.3 Skip Flags (Partial Re-Runs)
Because the script is idempotent, you can safely re-run it in full. However, if you want to skip specific sections during development, create a wrapper script that sources functions selectively:
# Example: Re-run only PHP configuration after tuning
source /root/setup_lemp_improved.sh
pre_flight_checks
install_php
configure_nginx
3.6 Troubleshooting Common Issues
3.6.1 Dpkg Lock Errors
Symptom: Could not get lock /var/lib/dpkg/lock-frontend
Cause: Another process (unattended-upgrades, another apt instance, or a crashed process) is holding the lock file.
Solution:
# Identify the process holding the lock
sudo lsof /var/lib/dpkg/lock-frontend
# Output example: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# unattended-upgr 1234 root 12uW REG 0,23 0 1234 /var/lib/dpkg/lock-frontend
# Option 1: Wait for the process to finish
sudo watch lsof /var/lib/dpkg/lock-frontend
# Option 2: Kill the stuck process (use with caution)
sudo kill -9 <PID>
# Option 3: Force-clear stale locks
sudo rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock
sudo dpkg --configure -a
sudo apt-get update
# Option 4: Reboot (nuclear option when all else fails)
sudo reboot
3.6.2 MySQL Broken Installation (iU State)
Symptom: mysql-server package shows iU in
dpkg -l output; MySQL service fails to start.
Cause: A previous installation was interrupted (network failure, OOM kill, manual abort), leaving MySQL in an inconsistent state.
Solution:
# Check MySQL package state
dpkg -l | grep mysql-server
# ii = installed and configured (good)
# iU = installed but unconfigured (bad)
# Full purge and reinstall
sudo dpkg --configure -a || true
sudo apt-get purge -y mysql-server mysql-client mysql-common
sudo apt-get autoremove -y
sudo rm -rf /var/lib/mysql /etc/mysql /var/log/mysql*
sudo apt-get update
sudo apt-get install -y mysql-server mysql-client
# Verify
sudo systemctl status mysql
sudo mysql -e "SELECT 1;"
3.6.3 OOM Kills (Out of Memory)
Symptom: Services randomly dying, system log shows
Out of memory: Kill process <PID> (OOM killer invoked).
Cause: Insufficient RAM for running services. OpenStack control plane alone consumes approximately 3-4 GB.
Solution:
# Check OOM killer log entries
sudo dmesg | grep -i "out of memory\|oom\|killed process"
sudo grep -i "oom" /var/log/syslog
# Check current memory usage
free -h
vmstat -s | head -5
# Create swap if not present (see Section 2.3)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Reduce MySQL memory footprint (if needed)
sudo mysql -e "SET GLOBAL innodb_buffer_pool_size = 536870912;" # 512MB
# Add to my.cnf for persistence
echo -e "[mysqld]\ninnodb_buffer_pool_size = 512M" | sudo tee /etc/mysql/mysql.conf.d/low-memory.cnf
sudo systemctl restart mysql
3.6.4 Port Conflicts
Symptom: Nginx fails to start with
bind() to 0.0.0.0:80 failed (98: Address already in use)
Cause: Another process (Apache, another Nginx instance, or OpenStack service) is already bound to port 80 or 443.
Solution:
# Identify what is using ports 80 and 443
sudo ss -tlnp | grep -E ':80|:443'
sudo lsof -i :80
sudo lsof -i :443
# If Apache is running, stop and disable it
sudo systemctl stop apache2
sudo systemctl disable apache2
# Restart Nginx
sudo systemctl restart nginx
sudo systemctl status nginx
3.7 Verification Commands
After the LEMP installation completes, verify each component:
3.7.1 Nginx
# Check service status
sudo systemctl is-active nginx
sudo systemctl status nginx --no-pager
# Check configuration syntax
sudo nginx -t
# Check version
nginx -v
# Test HTTP response
curl -I http://openstack.kuyaops.com
# Expected: HTTP/1.1 200 OK or 301 redirect to HTTPS
# Test HTTPS response
curl -I https://openstack.kuyaops.com
# Expected: HTTP/2 200 with valid SSL certificate
3.7.2 MySQL
# Check service status
sudo systemctl is-active mysql
# Login as root (uses auth_socket - no password needed from root user)
sudo mysql -e "SELECT VERSION();"
# Login as application user
sudo mysql -u openstack_user -p -e "SHOW DATABASES;"
# Check user authentication methods
sudo mysql -e "SELECT user, host, plugin FROM mysql.user;"
# Expected: root@localhost -> auth_socket, openstack_user@localhost -> caching_sha2_password
3.7.3 PHP-FPM
# Check service status
sudo systemctl is-active php8.3-fpm
# Check version
php -v
# Test PHP processing via Nginx
curl -s https://openstack.kuyaops.com/index.php | grep -oP 'PHP Version \d+\.\d+'
# Verify OPcache is enabled
php -i | grep opcache
# Check FPM pool socket
ls -la /run/php/php8.3-fpm.sock
3.7.4 Firewall (UFW)
# Check firewall status
sudo ufw status verbose
# Expected: 22/tcp ALLOW, Nginx Full ALLOW, default deny incoming
3.7.5 Fail2ban
# Check service status
sudo systemctl is-active fail2ban
# View active jails
sudo fail2ban-client status
# View sshd jail details
sudo fail2ban-client status sshd
4. OpenStack Installation
4.1 MicroStack vs. Full OpenStack
MicroStack is Canonical's streamlined, snap-packaged distribution of OpenStack. It bundles all essential OpenStack services into a single, self-contained snap that can be installed on a single machine. This is in contrast to a full OpenStack deployment (using tools like Kolla-Ansible, OpenStack-Ansible, or Charms) which requires multiple servers and complex orchestration.
| Aspect | MicroStack (All-in-One) | Full OpenStack |
|---|---|---|
| Installation time | 15-30 minutes | 2-8 hours |
| Server requirements | 1 droplet (8 GB RAM) | 3-9+ servers |
| Management complexity | Low; single snap command | High; requires orchestration expertise |
| Scalability | Limited to single node | Highly scalable to 1000s of nodes |
| Use case | Development, testing, learning | Production, large-scale deployments |
| Updates | Automatic via snap | Manual, service-by-service upgrades |
4.2 Why All-in-One for Development
An all-in-one deployment places the controller, compute, network, and storage services on a single node. While this violates production best practices, it is ideal for:
- Learning OpenStack — Understand all services on one machine
- CI/CD testing — Spin up VMs for integration tests
- Proof of concept — Validate OpenStack fits your workflow
- Development sandbox — Test applications in isolated VMs
4.3 Script Overview: install_openstack.sh
The OpenStack installation is automated via a bash script that handles pre-flight checks, MicroStack installation, networking configuration, image setup, and security group rules.
4.3.1 Key Features
| Feature | Description |
|---|---|
| RAM/Disk/CPU validation | Aborts if minimum resources are not available |
| Nested virtualization check | Detects KVM support; falls back to QEMU with performance warning |
| LEMP conflict detection | Warns if MySQL or Nginx binds to MicroStack's required ports |
| Automated networking | Creates external, internal, and router with NAT |
| Cloud image download | Downloads and verifies Ubuntu 22.04 cloud image for Glance |
| Flavor creation | Creates 5 standard flavors from m1.tiny to m1.xlarge |
| Security hardening | Configures security groups for SSH, HTTP, HTTPS, and ICMP |
| SSH keypair generation | Creates ~/.ssh/openstack_key for VM access |
| Test VM launch | Launches a test VM with floating IP to verify end-to-end |
| Nginx proxy setup | Configures Nginx reverse proxy to Horizon dashboard |
4.4 Complete OpenStack Installation Script
Save the following script as /root/install_openstack.sh:
#!/bin/bash
# =============================================================================
# OpenStack (MicroStack) Installation Script for Ubuntu 24.04
# Version: 2.0 (Production-Ready with Full Error Handling)
# Description: All-in-one OpenStack deployment with LEMP integration
# =============================================================================
set -euo pipefail
# -----------------------------------------------
# CONFIGURATION VARIABLES
# -----------------------------------------------
DOMAIN="openstack.kuyaops.com"
EXTERNAL_IF="eth0" # Adjust to your primary interface
FLOATING_IP_START="10.20.20.100" # Start of floating IP pool
FLOATING_IP_END="10.20.20.200" # End of floating IP pool
GATEWAY_IP="10.20.20.1" # MicroStack default gateway
DNS_SERVERS="8.8.8.8,1.1.1.1" # External DNS for VMs
INTERNAL_CIDR="192.168.222.0/24" # Private tenant network
IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
IMAGE_NAME="ubuntu-22.04"
SSH_KEY="$HOME/.ssh/openstack_key"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_err() { echo -e "${RED}[ERROR]${NC} $1"; }
# -----------------------------------------------
# PRE-FLIGHT CHECKS
# -----------------------------------------------
pre_flight_checks() {
log_info "Running OpenStack pre-flight checks..."
if [ "$EUID" -ne 0 ]; then
log_err "Must be run as root"
exit 1
fi
# Check RAM (8GB minimum)
local total_ram=$(free -m | awk '/^Mem:/{print $2}')
if [ "$total_ram" -lt 7168 ]; then
log_warn "RAM is ${total_ram}MB. 8GB+ strongly recommended for OpenStack."
read -p "Continue anyway? (y/N) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 1
fi
# Check disk space (40GB free minimum)
local free_disk=$(df -m / | awk 'NR==2{print $4}')
if [ "$free_disk" -lt 40960 ]; then
log_err "Insufficient disk space: ${free_disk}MB free (40GB minimum)"
exit 1
fi
# Check CPU
local cpu_count=$(nproc)
if [ "$cpu_count" -lt 2 ]; then
log_err "At least 2 vCPUs required (found $cpu_count)"
exit 1
fi
log_ok "Resources: ${cpu_count} vCPU, ${total_ram}MB RAM, ${free_disk}MB disk free"
# Check nested virtualization
if [ -e /dev/kvm ]; then
log_ok "KVM nested virtualization available (/dev/kvm)"
else
log_warn "KVM not available. VMs will use QEMU (slower performance)."
log_warn "To enable KVM: shutdown droplet, enable nested virt in DO panel, reboot."
fi
# Check for LEMP conflicts
if ss -tlnp | grep -q ':80 '; then
log_info "Port 80 in use by: $(ss -tlnp | grep ':80 ' | awk '{print $6}')"
log_info "Nginx will proxy to Horizon; this is expected."
fi
# Verify snapd is available
if ! command -v snap &>/dev/null; then
log_err "snapd is not installed. Install with: apt install -y snapd"
exit 1
fi
log_ok "snapd is available"
}
# -----------------------------------------------
# INSTALL MICROSTACK
# -----------------------------------------------
install_microstack() {
log_info "Installing MicroStack via snap..."
if snap list | grep -q microstack; then
log_info "MicroStack already installed. Refreshing..."
snap refresh microstack
else
snap install microstack --classic
fi
log_ok "MicroStack installed"
snap list microstack
}
# -----------------------------------------------
# INITIALIZE MICROSTACK
# -----------------------------------------------
init_microstack() {
log_info "Initializing MicroStack (this takes 10-20 minutes)..."
if [ -f /var/snap/microstack/common/etc/nova/nova.conf ]; then
log_info "MicroStack appears already initialized. Skipping init."
else
microstack init --auto --control
fi
log_ok "MicroStack initialized"
# Verify key services
local services="nova-compute neutron-server keystone glance-api"
for svc in $services; do
if snap services microstack | grep -q "$svc"; then
log_ok "Service $svc is present"
else
log_warn "Service $svc not found in MicroStack"
fi
done
}
# -----------------------------------------------
# CONFIGURE NETWORKING
# -----------------------------------------------
configure_networking() {
log_info "Configuring OpenStack networking..."
local openstack="microstack.openstack"
# Create external (provider) network
if ! $openstack network list | grep -q external; then
$openstack network create --external --provider-network-type flat \
--provider-physical-network physnet1 external
log_ok "External network created"
else
log_info "External network already exists"
fi
# Create external subnet
if ! $openstack subnet list | grep -q external-subnet; then
$openstack subnet create --network external --subnet-range 10.20.20.0/24 \
--gateway 10.20.20.1 --allocation-pool start=$FLOATING_IP_START,end=$FLOATING_IP_END \
--dns-nameserver 8.8.8.8 --dns-nameserver 1.1.1.1 external-subnet
log_ok "External subnet created"
else
log_info "External subnet already exists"
fi
# Create internal (tenant) network
if ! $openstack network list | grep -q internal; then
$openstack network create internal
log_ok "Internal network created"
else
log_info "Internal network already exists"
fi
# Create internal subnet
if ! $openstack subnet list | grep -q internal-subnet; then
$openstack subnet create --network internal --subnet-range $INTERNAL_CIDR \
--gateway 192.168.222.1 --dns-nameserver 8.8.8.8 internal-subnet
log_ok "Internal subnet created"
else
log_info "Internal subnet already exists"
fi
# Create router
if ! $openstack router list | grep -q main-router; then
$openstack router create main-router
$openstack router set --external-gateway external main-router
$openstack router add subnet main-router internal-subnet
log_ok "Router 'main-router' created with NAT"
else
log_info "Router already exists"
fi
log_ok "Networking configured"
}
# -----------------------------------------------
# UPLOAD CLOUD IMAGE
# -----------------------------------------------
upload_image() {
log_info "Uploading Ubuntu 22.04 cloud image..."
local openstack="microstack.openstack"
if $openstack image list | grep -q "$IMAGE_NAME"; then
log_info "Image '$IMAGE_NAME' already exists"
return 0
fi
local tmp_img="/tmp/${IMAGE_NAME}.img"
if [ ! -f "$tmp_img" ]; then
log_info "Downloading cloud image..."
wget -O "$tmp_img" "$IMAGE_URL"
fi
$openstack image create --disk-format qcow2 --container-format bare \
--file "$tmp_img" --public "$IMAGE_NAME"
log_ok "Image '$IMAGE_NAME' uploaded to Glance"
$openstack image list
}
# -----------------------------------------------
# CREATE FLAVORS
# -----------------------------------------------
create_flavors() {
log_info "Creating VM flavors..."
local openstack="microstack.openstack"
declare -A flavors=(
["m1.tiny"]="512 1 10"
["m1.small"]="1024 1 20"
["m1.medium"]="2048 2 40"
["m1.large"]="4096 4 80"
["m1.xlarge"]="8192 8 160"
)
for name in "${!flavors[@]}"; do
read -r ram vcpu disk <<< "${flavors[$name]}"
if $openstack flavor list | grep -q "$name"; then
log_info "Flavor '$name' already exists"
else
$openstack flavor create --ram $ram --vcpus $vcpu --disk $disk "$name"
log_ok "Flavor '$name' created: ${ram}MB RAM, ${vcpu} vCPU, ${disk}GB disk"
fi
done
}
# -----------------------------------------------
# CONFIGURE SECURITY GROUPS
# -----------------------------------------------
configure_security_groups() {
log_info "Configuring security groups..."
local openstack="microstack.openstack"
local default_sg=$($openstack security group list --project admin -c ID -f value | head -1)
if [ -z "$default_sg" ]; then
log_err "Could not find default security group"
return 1
fi
# SSH (port 22)
if ! $openstack security group rule list "$default_sg" | grep -q '22:22'; then
$openstack security group rule create --protocol tcp --dst-port 22:22 \
--remote-ip 0.0.0.0/0 "$default_sg"
log_ok "SSH (22) rule added"
fi
# HTTP (port 80)
if ! $openstack security group rule list "$default_sg" | grep -q '80:80'; then
$openstack security group rule create --protocol tcp --dst-port 80:80 \
--remote-ip 0.0.0.0/0 "$default_sg"
log_ok "HTTP (80) rule added"
fi
# HTTPS (port 443)
if ! $openstack security group rule list "$default_sg" | grep -q '443:443'; then
$openstack security group rule create --protocol tcp --dst-port 443:443 \
--remote-ip 0.0.0.0/0 "$default_sg"
log_ok "HTTPS (443) rule added"
fi
# ICMP (ping)
if ! $openstack security group rule list "$default_sg" | grep -q 'icmp'; then
$openstack security group rule create --protocol icmp \
--remote-ip 0.0.0.0/0 "$default_sg"
log_ok "ICMP rule added"
fi
log_ok "Security groups configured"
}
# -----------------------------------------------
# GENERATE SSH KEYPAIR
# -----------------------------------------------
generate_ssh_key() {
log_info "Generating SSH keypair..."
if [ ! -f "$SSH_KEY" ]; then
ssh-keygen -t ed25519 -f "$SSH_KEY" -N "" -C "openstack@$(hostname)"
chmod 600 "$SSH_KEY"
chmod 644 "${SSH_KEY}.pub"
log_ok "SSH keypair generated: $SSH_KEY"
else
log_info "SSH keypair already exists: $SSH_KEY"
fi
# Import into OpenStack
local openstack="microstack.openstack"
if ! $openstack keypair list | grep -q openstack_key; then
$openstack keypair create --public-key "${SSH_KEY}.pub" openstack_key
log_ok "Public key imported into OpenStack as 'openstack_key'"
else
log_info "Keypair 'openstack_key' already exists"
fi
}
# -----------------------------------------------
# LAUNCH TEST VM
# -----------------------------------------------
launch_test_vm() {
log_info "Launching test VM to verify deployment..."
local openstack="microstack.openstack"
local vm_name="test-vm"
# Delete existing test VM if present
if $openstack server list | grep -q "$vm_name"; then
log_info "Deleting existing test VM..."
$openstack server delete "$vm_name"
sleep 5
fi
# Launch VM
$openstack server create \
--image "$IMAGE_NAME" \
--flavor m1.small \
--network internal \
--key-name openstack_key \
--security-group default \
"$vm_name"
log_info "Waiting for VM to become active (this may take 2-5 minutes)..."
local attempts=0
while [ $attempts -lt 30 ]; do
local status=$($openstack server show "$vm_name" -c status -f value)
if [ "$status" = "ACTIVE" ]; then
log_ok "VM '$vm_name' is ACTIVE"
break
fi
log_info "VM status: $status (attempt $attempts/30)"
sleep 10
attempts=$((attempts + 1))
done
# Allocate and assign floating IP
log_info "Allocating floating IP..."
local fip=$($openstack floating ip create external -c floating_ip_address -f value)
$openstack server add floating ip "$vm_name" "$fip"
log_ok "Test VM launched with floating IP: $fip"
echo ""
echo "============================================="
echo " CONNECT TO TEST VM:"
echo " ssh -i $SSH_KEY ubuntu@$fip"
echo "============================================="
}
# -----------------------------------------------
# CONFIGURE NGINX REVERSE PROXY FOR HORIZON
# -----------------------------------------------
configure_nginx_proxy() {
log_info "Configuring Nginx reverse proxy for Horizon..."
local horizon_conf="/etc/nginx/sites-available/openstack-horizon"
cat > "$horizon_conf" << 'EOF'
# OpenStack Horizon Dashboard Proxy
server {
listen 80;
server_name openstack.kuyaops.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name openstack.kuyaops.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/openstack.kuyaops.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openstack.kuyaops.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/openstack.kuyaops.com/chain.pem;
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Proxy to Horizon (MicroStack internal IP)
location / {
proxy_pass http://10.20.20.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (for VNC console)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
}
# Static files (optional optimization)
location /static {
proxy_pass http://10.20.20.1/static;
proxy_cache_valid 200 1h;
}
}
EOF
ln -sf "$horizon_conf" /etc/nginx/sites-enabled/openstack-horizon
nginx -t && systemctl reload nginx
log_ok "Nginx proxy for Horizon configured"
}
# -----------------------------------------------
# PRINT SUMMARY
# -----------------------------------------------
print_summary() {
local openstack="microstack.openstack"
local admin_password=$(snap get microstack config.credentials.keystone-password 2>/dev/null || echo "unknown")
echo ""
echo "============================================="
echo " OPENSTACK INSTALLATION COMPLETE"
echo "============================================="
echo "Horizon Dashboard: https://$DOMAIN"
echo "Internal Horizon: http://10.20.20.1"
echo ""
echo "Login Credentials:"
echo " Username: admin"
echo " Password: $admin_password"
echo ""
echo "Networks:"
$openstack network list
echo ""
echo "Images:"
$openstack image list
echo ""
echo "Flavors:"
$openstack flavor list
echo ""
echo "SSH Key: $SSH_KEY"
echo "============================================="
}
# -----------------------------------------------
# MAIN EXECUTION
# -----------------------------------------------
main() {
log_info "Starting OpenStack installation..."
pre_flight_checks
install_microstack
init_microstack
configure_networking
upload_image
create_flavors
configure_security_groups
generate_ssh_key
configure_nginx_proxy
launch_test_vm
print_summary
log_ok "OpenStack installation complete!"
}
main "$@"
4.5 Running the Installation
# Download the script
curl -o /root/install_openstack.sh https://your-repo/install_openstack.sh
chmod +x /root/install_openstack.sh
# Run the installation (takes 20-40 minutes)
sudo /root/install_openstack.sh
# Monitor the installation in another terminal
sudo tail -f /var/log/syslog | grep -E "microstack|nova|neutron|glance"
4.6 Post-Installation Configuration
4.6.1 Networking Architecture
After installation, your OpenStack network topology looks like this:
Internet
|
| (Floating IPs: 10.20.20.100-200)
v
+------------+ +------------+ +------------+
| External |<------->| Router |<------->| Internal |
| Network | NAT | (main) | Route | Network |
|10.20.20.0/24| | | |192.168.222.0/24|
+------------+ +------------+ +------------+
|
+-----+-----+
| |
+---v---+ +---v---+
| VM 1 | | VM 2 |
|10.20.20.150| |192.168.222.5|
+--------+ +--------+
4.6.2 Verify Networking
# List networks
microstack.openstack network list
# Expected: external (provider), internal (private tenant)
# List subnets
microstack.openstack subnet list
# Expected: external-subnet, internal-subnet
# List routers
microstack.openstack router list
# Expected: main-router with external gateway
# Verify router NAT
microstack.openstack router show main-router -c external_gateway_info
4.6.3 Verify Images
# List uploaded images
microstack.openstack image list
# Expected: ubuntu-22.04 in 'active' state
# Show image details
microstack.openstack image show ubuntu-22.04
4.6.4 Verify Flavors
# List flavors
microstack.openstack flavor list
# Expected: m1.tiny, m1.small, m1.medium, m1.large, m1.xlarge
4.6.5 Verify Security Groups
# List security group rules
microstack.openstack security group rule list default
# Expected: TCP 22, 80, 443 from 0.0.0.0/0; ICMP from 0.0.0.0/0
4.6.6 Horizon Dashboard Access
Once the installation completes, access the Horizon dashboard:
# URL (via Nginx proxy with SSL)
https://openstack.kuyaops.com
# URL (direct, internal only)
http://10.20.20.1
# Default credentials
Username: admin
Password: (auto-generated; retrieve with:)
snap get microstack config.credentials.keystone-password
Note: The MicroStack snap auto-generates the admin password during
initialization. Store this password securely. You can retrieve it at any time with:
snap get microstack config.credentials.keystone-password
4.7 Nginx Reverse Proxy and SSL Configuration
The Horizon dashboard listens on http://10.20.20.1 (MicroStack's internal IP).
Nginx acts as a reverse proxy, providing SSL termination and public access through
https://openstack.kuyaops.com.
4.7.1 Proxy Configuration Details
The Nginx proxy configuration handles:
- SSL termination — HTTPS from clients, HTTP to Horizon
- WebSocket proxying — Required for the VNC console in Horizon
- Security headers — X-Forwarded-* headers for proper request handling
- HTTP/2 support — For improved performance
- Connection timeouts — Extended to 300s for long-running operations
4.7.2 Renewing SSL Certificates
# Test auto-renewal
certbot renew --dry-run
# Force renewal if needed
certbot renew --force-renewal
# Reload Nginx after renewal
certbot renew --deploy-hook "systemctl reload nginx"
# Verify certificate expiry date
echo | openssl s_client -servername openstack.kuyaops.com -connect openstack.kuyaops.com:443 2>/dev/null | openssl x509 -noout -dates
4.8 Launching Your First VM
After the test VM is launched by the script, you can manually create additional VMs:
# Step 1: Create a VM
microstack.openstack server create \
--image ubuntu-22.04 \
--flavor m1.medium \
--network internal \
--key-name openstack_key \
--security-group default \
my-first-vm
# Step 2: Wait for ACTIVE status
watch microstack.openstack server list
# Step 3: Allocate a floating IP
FIP=$(microstack.openstack floating ip create external -c floating_ip_address -f value)
microstack.openstack server add floating ip my-first-vm "$FIP"
echo "Floating IP: $FIP"
# Step 4: Connect via SSH
ssh -i ~/.ssh/openstack_key ubuntu@$FIP
# Step 5: Verify VM can reach the internet
ping -c 3 8.8.8.8
curl -I http://archive.ubuntu.com
5. Architecture Deep Dive
5.1 Single Node vs. Multi-Node
The all-in-one deployment is excellent for learning and development, but production workloads require a distributed architecture. This section compares the two approaches.
5.1.1 All-in-One (Current Deployment)
+-----------------------------------------------------+
| DigitalOcean Droplet |
| 4 vCPU / 8 GB RAM |
| |
| +----------------+ +----------------+ |
| | Controller | | Compute | |
| | Services | | (KVM/QEMU) | |
| | | | | |
| | Keystone | | Nova Compute | |
| | Nova API | | Libvirt | |
| | Neutron | | QEMU/KVM | |
| | Glance | +----------------+ |
| | Horizon | |
| | MySQL (int.) | +----------------+ |
| | RabbitMQ | | Networking | |
| +----------------+ | (OVS/Linux | |
| | Bridge) | |
| +----------------+ |
+-----------------------------------------------------+
5.1.2 Production Multi-Node (Recommended for Scale)
+-----------------------------------------------------+
| CONTROLLER NODE |
| 2 vCPU / 4 GB RAM |
| |
| Keystone + Glance + Nova API + Neutron Server |
| Horizon Dashboard + MySQL + RabbitMQ |
+-----------------------------------------------------+
|
+-----------------------------------------------------+
| COMPUTE NODE 1 |
| 4 vCPU / 8 GB RAM |
| |
| Nova Compute + Libvirt + KVM + Neutron Agent |
| (Can run 4-6 small VMs) |
+-----------------------------------------------------+
|
+-----------------------------------------------------+
| COMPUTE NODE 2 |
| 4 vCPU / 8 GB RAM |
| |
| Nova Compute + Libvirt + KVM + Neutron Agent |
| (Additional capacity for horizontal scaling) |
+-----------------------------------------------------+
5.2 When to Separate Components
| Indicator | Threshold | Action |
|---|---|---|
| Consistently high RAM usage | >85% for 15+ minutes | Separate compute node or upgrade droplet |
| High CPU steal time | >10% consistently | Move to CPU-optimized droplet or separate compute |
| VM launch time | >5 minutes consistently | Add dedicated compute node |
| Number of active VMs | >6 concurrent VMs | Add compute nodes; separate controller |
| Need for high availability | Production SLA requirements | Multi-node with redundant controllers |
| Storage requirements | >500 GB persistent storage | Add dedicated storage node (Ceph) |
5.3 Cost Analysis
| Architecture | Components | Monthly Cost (DigitalOcean) | VM Capacity |
|---|---|---|---|
| All-in-One (Current) | 1x 8GB droplet | $48/mo | 2-4 small VMs |
| Basic Split | 1x 4GB LEMP + 1x 8GB OpenStack | $72/mo | 3-5 small VMs |
| Recommended Split | 1x 4GB LEMP + 1x 16GB OpenStack | $120/mo | 6-10 small VMs |
| Multi-Node Basic | 1x 4GB Controller + 2x 8GB Compute | $168/mo | 8-12 VMs across 2 compute nodes |
| Multi-Node HA | 3x 4GB Controller + 2x 16GB Compute + 1x 16GB Storage | $504/mo | 12-20 VMs with HA storage |
Note: The "Basic Split" architecture separates the LEMP web server onto its own $12/mo droplet, isolating the public-facing web services from the OpenStack control plane. This improves security and allows independent scaling. Combined with the OpenStack droplet ($48/mo), the total is $60/mo, only $12 more than the all-in-one approach.
5.4 Nested Virtualization Considerations
Nested virtualization allows VMs running inside OpenStack (which itself runs on a DigitalOcean droplet) to use hardware-accelerated CPU virtualization extensions. Without it, OpenStack falls back to QEMU software emulation, which is significantly slower.
5.4.1 Check Nested Virtualization Support
# Check if /dev/kvm exists
ls -la /dev/kvm
# If present: KVM is available
# Check CPU flags
grep -E "vmx|svm" /proc/cpuinfo
# vmx = Intel VT-x support
# svm = AMD-V support
# Check if KVM module is loaded
lsmod | grep kvm
5.4.2 Enabling Nested Virtualization on DigitalOcean
Nested virtualization is not enabled by default on all DigitalOcean droplet plans. It is typically available on:
- CPU-Optimized droplets (all sizes)
- General Purpose droplets (4 vCPU and above)
# If KVM is not available, you must:
# 1. Power off the droplet
sudo poweroff
# 2. In the DigitalOcean control panel:
# - Navigate to Droplets > Your Droplet > Kernel
# - Select a kernel with KVM support
# - Power the droplet back on
# 3. After reboot, verify:
ls -la /dev/kvm
5.4.3 QEMU Fallback Performance
If KVM is not available, QEMU software emulation is used. Expect:
- 50-70% slower CPU performance
- Longer VM boot times (2-5x)
- Higher CPU usage on the host
For development and testing, QEMU is acceptable. For production workloads, KVM is mandatory.
5.5 RAM Budget Breakdown (8 GB Droplet)
Understanding how RAM is allocated helps you plan VM capacity and troubleshoot OOM issues.
| Service/Component | RAM Usage | Notes |
|---|---|---|
| Ubuntu 24.04 OS + kernel | ~512 MB | Base system overhead |
| Nginx | ~50 MB | Lightweight; scales with connections |
| MySQL (OpenStack internal) | ~512 MB | Can be reduced to 256 MB if needed |
| Keystone | ~256 MB | Authentication service |
| Nova (API + Compute) | ~512 MB | Compute service |
| Neutron | ~384 MB | Networking service + OVS |
| Glance | ~256 MB | Image service |
| Horizon | ~256 MB | Dashboard (Apache/mod_wsgi) |
| RabbitMQ | ~256 MB | Message broker |
| Memcache | ~128 MB | Token caching |
| Control Plane Total | ~3.4 GB | Reserved for OpenStack services |
| Available for VMs | ~3-4 GB | After 4 GB swap buffer |
With approximately 3-4 GB available for VMs, you can run:
- 1x m1.medium (2 GB) + 2x m1.small (1 GB each) = 4 GB (with some swap)
- 3x m1.small (1 GB each) = 3 GB
- 6x m1.tiny (512 MB each) = 3 GB
6. Access and Management
6.1 Horizon Dashboard Access
| Attribute | Value |
|---|---|
| Public URL | https://openstack.kuyaops.com |
| Internal URL | http://10.20.20.1 |
| Username | admin |
| Password | snap get microstack config.credentials.keystone-password |
| Domain | default |
6.1.1 Logging Into Horizon
- Navigate to
https://openstack.kuyaops.com - Enter username:
admin - Enter password (retrieve with snap command above)
- Select domain:
default - Click "Sign In"
6.2 OpenStack CLI Commands
MicroStack provides a wrapped OpenStack CLI via microstack.openstack. All standard
OpenStack CLI commands work through this wrapper.
6.2.1 Authentication
# Set credentials for CLI (run once per session)
export OS_USERNAME=admin
export OS_PASSWORD=$(snap get microstack config.credentials.keystone-password)
export OS_AUTH_URL=http://10.20.20.1:5000/v3
export OS_PROJECT_NAME=admin
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_IDENTITY_API_VERSION=3
# Verify authentication
microstack.openstack token issue
6.2.2 Server (VM) Management
# List all VMs
microstack.openstack server list
# Show VM details
microstack.openstack server show <vm-name>
# Create a VM
microstack.openstack server create \
--image ubuntu-22.04 \
--flavor m1.small \
--network internal \
--key-name openstack_key \
my-new-vm
# Start / stop / reboot a VM
microstack.openstack server start <vm-name>
microstack.openstack server stop <vm-name>
microstack.openstack server reboot <vm-name>
# Delete a VM
microstack.openstack server delete <vm-name>
# View VM console log
microstack.openstack console log show <vm-name>
# Get VNC console URL
microstack.openstack console url show --vnc <vm-name>
6.2.3 Floating IP Management
# List floating IPs
microstack.openstack floating ip list
# Allocate a new floating IP
microstack.openstack floating ip create external
# Associate floating IP with VM
microstack.openstack server add floating ip <vm-name> <floating-ip>
# Remove floating IP
microstack.openstack server remove floating ip <vm-name> <floating-ip>
# Release floating IP
microstack.openstack floating ip delete <floating-ip>
6.2.4 Image Management
# List images
microstack.openstack image list
# Upload a new image
microstack.openstack image create \
--disk-format qcow2 \
--container-format bare \
--file /path/to/image.qcow2 \
my-custom-image
# Delete an image
microstack.openstack image delete <image-name>
# Show image details
microstack.openstack image show <image-name>
6.2.5 Network and Security Management
# List networks
microstack.openstack network list
# List subnets
microstack.openstack subnet list
# List routers
microstack.openstack router list
# List security groups
microstack.openstack security group list
# List security group rules
microstack.openstack security group rule list <group-name>
# Create a new security group rule
microstack.openstack security group rule create \
--protocol tcp --dst-port 8080:8080 \
--remote-ip 0.0.0.0/0 \
<security-group-id>
6.3 SSH Access to VMs
VMs on the internal network are accessible via their floating IPs using the SSH keypair generated during installation.
# Basic SSH connection (Ubuntu cloud images use 'ubuntu' user)
ssh -i ~/.ssh/openstack_key ubuntu@<floating-ip>
# With verbose output (for troubleshooting)
ssh -vvv -i ~/.ssh/openstack_key ubuntu@<floating-ip>
# If you get "UNPROTECTED PRIVATE KEY FILE" warning
chmod 600 ~/.ssh/openstack_key
ssh -i ~/.ssh/openstack_key ubuntu@<floating-ip>
# Using SSH config (add to ~/.ssh/config for convenience)
cat >> ~/.ssh/config << 'EOF'
Host openstack-vm
HostName <floating-ip>
User ubuntu
IdentityFile ~/.ssh/openstack_key
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
EOF
# Then connect simply with:
ssh openstack-vm
Warning: Setting StrictHostKeyChecking no in SSH config is
convenient for development but should not be used in production. For production, properly manage
your known_hosts file.
6.4 Common Management Tasks
6.4.1 Check OpenStack Service Status
# List all MicroStack snap services
snap services microstack
# Check individual service status
snap services microstack | grep -E "nova|neutron|keystone|glance"
6.4.2 Restart OpenStack Services
# Restart a specific service
sudo snap restart microstack.nova-compute
sudo snap restart microstack.neutron-api
sudo snap restart microstack.keystone
# Restart all MicroStack services
sudo snap restart microstack
6.4.3 View Service Logs
# View logs for a specific service
sudo snap logs microstack.nova-compute
sudo snap logs microstack.neutron-server
sudo snap logs microstack.keystone
# Follow logs in real-time
sudo snap logs microstack.nova-compute -f
6.4.4 Resource Quotas
# Show current quotas
microstack.openstack quota show
# Update quotas (e.g., allow 20 instances)
microstack.openstack quota set --instances 20 admin
6.4.5 Backup and Export MicroStack
# Export MicroStack configuration
snap get microstack > /root/microstack-config-backup.txt
# Backup database
sudo cp /var/snap/microstack/common/var/lib/mysql /root/microstack-mysql-backup -r
# Backup Glance images
sudo cp /var/snap/microstack/common/var/lib/glance/images /root/microstack-images-backup -r
7. Troubleshooting Guide
This section provides systematic troubleshooting steps for the most common issues encountered during and after OpenStack deployment.
7.1 Service Not Starting
7.1.1 MicroStack Service Fails to Start
# 1. Check the service status
snap services microstack | grep -E "SERVICE|nova|neutron|keystone"
# 2. View recent logs for the failing service
sudo snap logs microstack.<service-name>
# Example:
sudo snap logs microstack.nova-compute
# 3. Check for port conflicts
sudo ss -tlnp | grep -E "8774|9696|5000|9292"
# 4. Restart the service
sudo snap restart microstack.<service-name>
# 5. If all else fails, restart all MicroStack services
sudo snap restart microstack
# 6. For persistent failures, check the system log
sudo journalctl -u snap.microstack.* --no-pager -n 50
7.1.2 MySQL Service Inside MicroStack Fails
# Check MySQL process
ps aux | grep mysql
# Check MySQL socket
ls -la /var/snap/microstack/common/var/lib/mysql/mysql.sock
# Restart MySQL within MicroStack
sudo snap restart microstack.mysql
# If corrupted, check disk space (MySQL fails if disk is full)
df -h /var/snap/microstack/
# Emergency: Reset MicroStack (destroys all data)
sudo snap remove microstack --purge
sudo snap install microstack --classic
microstack init --auto --control
7.1.3 Nginx Fails to Start
# Check Nginx configuration syntax
sudo nginx -t
# Check for port conflicts
sudo ss -tlnp | grep -E ':80|:443'
# Check Nginx error log
sudo tail -n 30 /var/log/nginx/error.log
# Check for SELinux/AppArmor denials
sudo dmesg | grep -i nginx
sudo aa-status | grep nginx
# Fix: If another service binds to port 80/443
sudo systemctl stop apache2 # or other conflicting service
sudo systemctl disable apache2
sudo systemctl restart nginx
7.2 Network Issues
7.2.1 VMs Cannot Reach the Internet
# 1. Verify the external network is configured correctly
microstack.openstack network show external
# Look for: provider:physical_network = physnet1
# router:external = true
# 2. Verify the router has an external gateway
microstack.openstack router show main-router -c external_gateway_info
# Should show: "network_id": "<external-network-uuid>"
# 3. Verify the router is connected to the internal subnet
microstack.openstack router show main-router -c interfaces_info
# 4. Check if NAT is working on the host
sudo iptables -t nat -L -n -v | grep MASQUERADE
# 5. Check IP forwarding is enabled
sysctl net.ipv4.ip_forward
# Expected: net.ipv4.ip_forward = 1
# 6. If IP forwarding is disabled, enable it:
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
# 7. Verify the VM has a default route
ssh -i ~/.ssh/openstack_key ubuntu@<floating-ip> 'ip route show'
# Expected: default via 192.168.222.1
7.2.2 Cannot Assign Floating IP
# 1. Check available floating IPs
microstack.openstack floating ip list
# 2. Check the external network's allocation pool
microstack.openstack subnet show external-subnet -c allocation_pools
# 3. If no IPs available, create more by expanding the pool
microstack.openstack subnet set --allocation-pool start=10.20.20.201,end=10.20.20.250 external-subnet
# 4. Verify the VM's port is on the internal network
microstack.openstack port list --device-id <vm-uuid>
7.2.3 DNS Resolution Fails Inside VMs
# 1. Check DNS configuration on the internal subnet
microstack.openstack subnet show internal-subnet -c dns_nameservers
# 2. Update DNS servers if needed
microstack.openstack subnet set --dns-nameserver 8.8.8.8 --dns-nameserver 1.1.1.1 internal-subnet
# 3. Inside the VM, test DNS resolution
ssh -i ~/.ssh/openstack_key ubuntu@<floating-ip>
systemd-resolve --status
cat /etc/resolv.conf
nslookup google.com 8.8.8.8
7.3 VM Won't Boot
7.3.1 VM Stuck in BUILD or ERROR State
# 1. Check VM status and fault message
microstack.openstack server show <vm-name> -c status -c fault
# 2. Check Nova compute logs
sudo snap logs microstack.nova-compute -n 50
# 3. Check if KVM is available
ls -la /dev/kvm
# If missing, QEMU will be used (slower but should still work)
# 4. Check disk space on the host
df -h /var/snap/microstack/common/
# 5. Check libvirt logs
sudo cat /var/snap/microstack/common/log/libvirt/qemu/<vm-name>.log 2>/dev/null
# 6. Check if the image is valid
microstack.openstack image show <image-name> -c status
# Must be: active
# 7. Delete and recreate the VM
microstack.openstack server delete <vm-name>
microstack.openstack server create \
--image ubuntu-22.04 --flavor m1.small \
--network internal --key-name openstack_key <vm-name>
7.3.2 VM Boots but Cannot Access via SSH
# 1. Check the VM's console log for boot messages
microstack.openstack console log show <vm-name>
# 2. Verify the security group allows SSH (port 22)
microstack.openstack security group rule list default
# 3. Verify a floating IP is assigned
microstack.openstack server show <vm-name> -c addresses
# 4. Test SSH with verbose output
ssh -vvv -i ~/.ssh/openstack_key ubuntu@<floating-ip>
# 5. Check if the SSH key was properly injected
# Look in console log for: "Authorized keys from /root/.ssh/authorized_keys"
# 6. Try accessing via VNC console through Horizon
# Navigate to Horizon > Instances > Click VM Name > Console Tab
# Login with: ubuntu / (no password, or check cloud-init output)
7.3.3 QEMU Performance Issues (Slow VM Boot)
# 1. Confirm KVM is NOT available
ls -la /dev/kvm # Should return "No such file or directory"
# 2. Check CPU steal time (high = noisy neighbor)
top -bn1 | grep "Cpu(s)" | awk '{print $10}' | sed 's/%.*//'
# If steal time > 10%, upgrade to a dedicated CPU droplet
# 3. Reduce VM resource usage
# Use m1.tiny or m1.small flavors instead of larger ones
# 4. Enable KVM (requires droplet shutdown + DO panel change)
# See Section 5.4.2 for instructions
7.4 Horizon Not Accessible
7.4.1 Horizon Returns 502 Bad Gateway
# 1. Check if Horizon service is running inside MicroStack
sudo snap services microstack | grep -i horizon
# 2. Check if the Keystone authentication service is running
sudo snap services microstack | grep -i keystone
# 3. Verify Nginx can reach Horizon
sudo curl -s http://10.20.20.1 --connect-timeout 5
# Expected: Should return Horizon HTML (even if redirect)
# 4. Check Nginx error log
sudo tail -n 20 /var/log/nginx/error.log
# 5. Restart MicroStack services
sudo snap restart microstack.horizon 2>/dev/null || true
sudo snap restart microstack.keystone
sudo snap restart microstack
# 6. Check if the issue is SSL-related
curl -vk https://openstack.kuyaops.com 2>&1 | grep -E "SSL|error|connected"
7.4.2 Horizon Login Fails
# 1. Verify the admin password
snap get microstack config.credentials.keystone-password
# 2. Test authentication via CLI
microstack.openstack token issue
# 3. Check Keystone logs
sudo snap logs microstack.keystone -n 30
# 4. Reset admin password (if needed)
sudo snap set microstack config.credentials.keystone-password=<new-password>
sudo snap restart microstack.keystone
# 5. Clear browser cookies and cache, then retry
7.4.3 VNC Console Not Working in Horizon
# 1. VNC requires WebSocket proxying in Nginx
# Verify the Nginx config includes WebSocket support:
grep -A 3 "Upgrade" /etc/nginx/sites-enabled/openstack-horizon
# 2. The config should contain:
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# 3. Reload Nginx after confirming
sudo nginx -t && sudo systemctl reload nginx
# 4. Check Nova novncproxy logs
sudo snap logs microstack.nova-novncproxy -n 20
7.5 SSL Certificate Issues
7.5.1 Certificate Expired
# 1. Check certificate expiry
openssl x509 -in /etc/letsencrypt/live/openstack.kuyaops.com/cert.pem -noout -dates
# 2. Renew manually
sudo certbot renew --force-renewal
# 3. Reload Nginx
sudo systemctl reload nginx
# 4. Verify the new certificate
echo | openssl s_client -connect openstack.kuyaops.com:443 -servername openstack.kuyaops.com 2>/dev/null | openssl x509 -noout -dates
7.5.2 Certbot Fails Domain Validation
# 1. Verify domain resolves to this server
dig +short openstack.kuyaops.com
# Should return your droplet's public IP
# 2. Verify port 80 is accessible from the internet
# (Temporarily allow if UFW is blocking)
sudo ufw allow 80/tcp
# 3. Check Nginx is listening on port 80
sudo ss -tlnp | grep ':80'
# 4. Try Certbot with standalone mode (stops Nginx temporarily)
sudo certbot certonly --standalone -d openstack.kuyaops.com
# 5. Update Nginx to use the new certificate
# (Update paths in /etc/nginx/sites-enabled/openstack-horizon)
sudo nginx -t && sudo systemctl reload nginx
7.6 Full Reset and Reinstall Procedure
If the OpenStack installation becomes completely corrupted, follow this procedure to perform a clean reset. This destroys all VMs, networks, and data.
7.6.1 Preserve Important Data
# Export MicroStack configuration
snap get microstack > /root/microstack-config-backup-$(date +%Y%m%d).txt
# Export any custom images
microstack.openstack image list
# Download custom images manually if needed
# Note floating IPs (will be lost)
microstack.openstack floating ip list
7.6.2 Remove MicroStack
# Stop all MicroStack services first
sudo snap stop microstack
# Remove MicroStack (removes all data)
sudo snap remove microstack --purge
# Verify removal
snap list | grep microstack # Should return nothing
7.6.3 Clean Up Remaining Artifacts
# Remove residual directories (optional)
sudo rm -rf /var/snap/microstack/
# Remove any leftover bridge interfaces
ip link show | grep brq
# sudo ip link delete <bridge-name> # if needed
# Flush iptables NAT rules (CAUTION: affects all NAT)
sudo iptables -t nat -F
sudo iptables -t nat -X
# Re-enable IP forwarding
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
7.6.4 Reinstall MicroStack
# Reinstall
sudo snap install microstack --classic
# Reinitialize
sudo microstack init --auto --control
# Verify
microstack.openstack service list
microstack.openstack hypervisor list
7.6.5 Reconfigure Networking and Images
# Re-run the configuration sections of the script
# (Or run the full install_openstack.sh again)
sudo /root/install_openstack.sh
Warning: The --purge flag irreversibly deletes all MicroStack
data including VMs, volumes, networks, and images. Always export any important data before
performing a full reset.
8. Security Best Practices
8.1 Security Architecture Overview
A single-node deployment concentrates all services on one machine, making security especially critical. The following layers provide defense in depth:
- Network firewall (UFW) — Controls traffic at the OS level
- Intrusion prevention (Fail2ban) — Detects and blocks malicious patterns
- Transport encryption (SSL/TLS) — Protects data in transit
- Authentication (OpenStack Keystone) — Identity management
- Application security (Security Groups) — Per-VM firewall rules
- Credential management — Secure storage of passwords and keys
8.2 Firewall Rules (UFW)
UFW (Uncomplicated Firewall) is the primary network-level defense. After the LEMP installation, the following rules are active:
8.2.1 Default UFW Configuration
# View current rules
sudo ufw status verbose
# Expected output:
# Status: active
# Logging: on (low)
# Default: deny (incoming), allow (outgoing), disabled (routed)
# To Action From
# -- ------ ----
# 22/tcp ALLOW IN Anywhere
# 80/tcp ALLOW IN Anywhere
# 443/tcp ALLOW IN Anywhere
# 22/tcp (v6) ALLOW IN Anywhere (v6)
# 80/tcp (v6) ALLOW IN Anywhere (v6)
# 443/tcp (v6) ALLOW IN Anywhere (v6)
8.2.2 Restricting SSH Access (Recommended)
# Remove blanket SSH allow
sudo ufw delete allow 22/tcp
# Allow SSH only from your IP address
sudo ufw allow from <your-home-ip>/32 to any port 22 proto tcp
# If you have multiple admin IPs
sudo ufw allow from <office-ip>/32 to any port 22 proto tcp
sudo ufw allow from <vpn-ip>/32 to any port 22 proto tcp
# Verify
sudo ufw status numbered
8.2.3 Additional Rules for Production
# Rate limit SSH (built-in UFW feature)
sudo ufw limit 22/tcp
# Block a specific IP address
sudo ufw deny from <malicious-ip>
# Allow specific IP ranges (e.g., office network)
sudo ufw allow from 203.0.113.0/24
# Reload firewall
sudo ufw reload
8.3 Fail2ban Configuration
Fail2ban monitors log files and bans IP addresses that show malicious patterns (brute-force login attempts, vulnerability scans, etc.).
8.3.1 Active Jails
| Jail | Purpose | Max Retries | Ban Time |
|---|---|---|---|
| sshd | SSH brute-force protection | 3 | 1 hour |
| nginx-http-auth | Basic auth brute-force | 5 | 1 hour |
| nginx-badbots | Blocks known malicious bots/crawlers | 2 | 1 hour |
| nginx-noscript | Blocks script vulnerability probes | 6 | 1 hour |
8.3.2 Fail2ban Management Commands
# View all active jails and their status
sudo fail2ban-client status
# View detailed status of a specific jail
sudo fail2ban-client status sshd
# Shows: number of currently banned IPs, total banned, file list
# List currently banned IPs
sudo fail2ban-client status sshd | grep "Banned IP list"
# Manually ban an IP
sudo fail2ban-client set sshd banip <ip-address>
# Manually unban an IP
sudo fail2ban-client set sshd unbanip <ip-address>
# Restart fail2ban after configuration changes
sudo systemctl restart fail2ban
# View fail2ban log
sudo tail -n 50 /var/log/fail2ban.log
8.4 SSL/TLS Encryption
8.4.1 SSL Configuration Details
The Nginx configuration enforces modern TLS settings:
# SSL protocols (TLS 1.2 minimum)
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites (strong, forward-secret)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
# Session caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Certificate paths (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/openstack.kuyaops.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openstack.kuyaops.com/privkey.pem;
8.4.2 SSL Test and Verification
# Test SSL configuration
openssl s_client -connect openstack.kuyaops.com:443 -servername openstack.kuyaops.com < /dev/null
# Test with SSL Labs (from any browser)
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=openstack.kuyaops.com
# Check certificate expiry (set up monitoring for this)
echo | openssl s_client -connect openstack.kuyaops.com:443 2>/dev/null | \
openssl x509 -noout -enddate
# Auto-renewal is configured via systemd timer
cat /etc/cron.d/certbot # or
systemctl list-timers | grep certbot
8.4.3 HTTP Strict Transport Security (HSTS)
Add HSTS to force browsers to always use HTTPS:
# Add to the Nginx SSL server block
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Reload Nginx
sudo nginx -t && sudo systemctl reload nginx
8.5 Strong Passwords and Credential Storage
8.5.1 Credential Files
# LEMP credentials
/root/.lemp_credentials
# Permissions: -rw------- (600)
# Owner: root
# Contents:
# DB_NAME=openstack_db
# DB_USER=openstack_user
# DB_PASSWORD=<random-24-char>
# MYSQL_ROOT_PASSWORD=<random-24-char>
# DOMAIN=openstack.kuyaops.com
# PHP_VERSION=8.3
# Verify permissions
ls -la /root/.lemp_credentials
# Should show: -rw------- 1 root root
8.5.2 OpenStack Admin Password
# Retrieve the admin password
snap get microstack config.credentials.keystone-password
# Change the admin password
snap set microstack config.credentials.keystone-password=<new-strong-password>
snap restart microstack.keystone
# Generate a strong password (24 characters)
openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 24
8.5.3 Security Recommendations
- Never commit credential files to version control
- Never paste credentials in chat, email, or logs
- Rotate passwords every 90 days
- Use a password manager for storing credentials
- Enable two-factor authentication for DigitalOcean account
8.6 OpenStack Security Group Rules
Security groups act as virtual firewalls for your VMs. The default security group configured during installation allows:
# View default security group rules
microstack.openstack security group rule list default
# Typical output:
# +--------------------------------------+-------------+-----------+------------+--------------------------------------+
# | ID | IP Protocol | IP Range | Port Range | Security Group |
# +--------------------------------------+-------------+-----------+------------+--------------------------------------+
# | xxxx | tcp | 0.0.0.0/0 | 22:22 | default |
# | xxxx | tcp | 0.0.0.0/0 | 80:80 | default |
# | xxxx | tcp | 0.0.0.0/0 | 443:443 | default |
# | xxxx | icmp | 0.0.0.0/0 | any | default |
# +--------------------------------------+-------------+-----------+------------+--------------------------------------+
8.6.1 Restricting SSH to Specific IPs
# Remove the blanket SSH rule
microstack.openstack security group rule list default
# Note the ID of the SSH rule (port 22)
microstack.openstack security group rule delete <ssh-rule-id>
# Add a restricted SSH rule
microstack.openstack security group rule create \
--protocol tcp --dst-port 22:22 \
--remote-ip <your-office-ip>/32 \
default
# Add additional IPs as needed
microstack.openstack security group rule create \
--protocol tcp --dst-port 22:22 \
--remote-ip <your-home-ip>/32 \
default
8.6.2 Creating a Custom Security Group
# Create a new security group
microstack.openstack security group create web-servers \
--description "Security group for web server VMs"
# Add rules
microstack.openstack security group rule create \
--protocol tcp --dst-port 80:80 \
--remote-ip 0.0.0.0/0 web-servers
microstack.openstack security group rule create \
--protocol tcp --dst-port 443:443 \
--remote-ip 0.0.0.0/0 web-servers
microstack.openstack security group rule create \
--protocol tcp --dst-port 22:22 \
--remote-ip <your-admin-ip>/32 web-servers
# Apply to a VM
microstack.openstack server add security group my-web-vm web-servers
# Remove default group if desired
microstack.openstack server remove security group my-web-vm default
8.7 Regular Updates
8.7.1 System Updates
# Update package lists
sudo apt update
# Review available upgrades
apt list --upgradable
# Apply upgrades
sudo apt upgrade -y
# Reboot if kernel was updated
[ -f /var/run/reboot-required ] && sudo reboot
8.7.2 MicroStack Updates
# Refresh MicroStack snap
sudo snap refresh microstack
# Check snap updates
snap list --all | grep microstack
# View snap change history
snap changes
8.7.3 Nginx and SSL Updates
# Update Nginx
sudo apt update && sudo apt install --only-upgrade nginx
# Test Nginx configuration
sudo nginx -t
# Reload if valid
sudo systemctl reload nginx
# Renew SSL certificate
certbot renew --dry-run
Recommendation: Set up unattended security updates for critical packages while excluding MicroStack (which may require manual testing before upgrades):
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
9. Quick Reference
9.1 Important File Paths
| File/Directory | Purpose |
|---|---|
/root/setup_lemp_improved.sh |
LEMP stack installation script |
/root/install_openstack.sh |
OpenStack installation script |
/root/.lemp_credentials |
LEMP database credentials (chmod 600) |
/root/lemp-backups/ |
Timestamped LEMP configuration backups |
/root/microstack-config-backup.txt |
MicroStack configuration export |
~/.ssh/openstack_key |
SSH private key for VM access |
~/.ssh/openstack_key.pub |
SSH public key imported into OpenStack |
/etc/nginx/sites-available/openstack.kuyaops.com |
Nginx virtual host config for LEMP |
/etc/nginx/sites-available/openstack-horizon |
Nginx reverse proxy config for Horizon |
/var/www/openstack.kuyaops.com/ |
Web root directory |
/etc/php/8.3/fpm/php.ini |
PHP-FPM configuration |
/etc/php/8.3/fpm/conf.d/10-opcache.ini |
OPcache configuration |
/etc/mysql/mysql.conf.d/ |
MySQL configuration snippets |
/etc/ufw/before.rules |
UFW firewall rules (pre-custom) |
/etc/fail2ban/jail.local |
Fail2ban jail configuration |
/swapfile |
4 GB swap file |
/var/log/nginx/ |
Nginx access and error logs |
/var/log/mysql/ |
MySQL logs |
/var/snap/microstack/common/ |
MicroStack data directory |
/etc/letsencrypt/live/openstack.kuyaops.com/ |
SSL certificates |
9.2 Important Commands
9.2.1 System Commands
# System resource monitoring
free -h # RAM and swap usage
df -h # Disk usage
htop # Interactive process viewer
vmstat 1 # System activity (1-second intervals)
iostat -x 1 # I/O statistics
# Check nested virtualization
ls -la /dev/kvm
grep -E "vmx|svm" /proc/cpuinfo
# IP forwarding check
sysctl net.ipv4.ip_forward
# Service management
systemctl status <service>
systemctl restart <service>
systemctl enable <service>
journalctl -u <service> --no-pager -n 50
9.2.2 Snap Commands
snap list # List installed snaps
snap list microstack # Show MicroStack version
snap services microstack # List MicroStack services
snap restart microstack # Restart all MicroStack services
snap restart microstack.<service> # Restart specific service
snap logs microstack.<service> # View service logs
snap logs microstack.<service> -f # Follow logs in real-time
snap refresh microstack # Update MicroStack
snap get microstack # Show MicroStack config
snap get microstack config.credentials.keystone-password
snap remove microstack --purge # Uninstall MicroStack
9.2.3 OpenStack CLI Commands
# Authentication
microstack.openstack token issue
# Servers (VMs)
microstack.openstack server list
microstack.openstack server show <name>
microstack.openstack server create --image <img> --flavor <flv> --network internal <name>
microstack.openstack server start|stop|reboot|delete <name>
microstack.openstack server add floating ip <name> <ip>
microstack.openstack server add security group <name> <group>
microstack.openstack console log show <name>
microstack.openstack console url show --vnc <name>
# Images
microstack.openstack image list
microstack.openstack image show <name>
microstack.openstack image create --disk-format qcow2 --container-format bare --file <file> <name>
microstack.openstack image delete <name>
# Networks
microstack.openstack network list
microstack.openstack network create|delete <name>
microstack.openstack subnet list
microstack.openstack subnet create|delete <name>
microstack.openstack router list
microstack.openstack router create|delete <name>
microstack.openstack router add subnet <router> <subnet>
microstack.openstack router set --external-gateway <network> <router>
# Floating IPs
microstack.openstack floating ip list
microstack.openstack floating ip create external
microstack.openstack floating ip delete <ip>
# Flavors
microstack.openstack flavor list
microstack.openstack flavor create --ram <MB> --vcpus <n> --disk <GB> <name>
# Security Groups
microstack.openstack security group list
microstack.openstack security group rule list <group>
microstack.openstack security group rule create --protocol tcp --dst-port <port> --remote-ip <cidr> <group>
microstack.openstack security group rule delete <rule-id>
# Keypairs
microstack.openstack keypair list
microstack.openstack keypair create --public-key <pubkey-file> <name>
# Quotas
microstack.openstack quota show
microstack.openstack quota set --instances <n> admin
9.2.4 Nginx Commands
nginx -v # Show version
nginx -t # Test configuration
nginx -T # Show full config (with includes)
sudo systemctl start|stop|restart|reload nginx
sudo systemctl status nginx
curl -I https://openstack.kuyaops.com # Test HTTPS response
9.2.5 MySQL Commands
sudo mysql -e "SELECT VERSION();" # Show MySQL version
sudo mysql -u root -p -e "SHOW DATABASES;"
sudo mysql -u openstack_user -p -e "SHOW TABLES;" <database>
sudo systemctl status mysql
sudo systemctl restart mysql
9.2.6 PHP Commands
php -v # Show PHP version
php -i | grep opcache # Check OPcache status
php -m # List loaded modules
sudo systemctl status php8.3-fpm
sudo systemctl restart php8.3-fpm
9.3 Port Reference Table
| Port | Protocol | Service | Source | Notes |
|---|---|---|---|---|
| 22 | TCP | SSH | Admin IPs | Remote server administration |
| 80 | TCP | HTTP | Any | Redirects to HTTPS |
| 443 | TCP | HTTPS | Any | Horizon dashboard + web apps |
| 3306 | TCP | MySQL | localhost | Not exposed externally (UFW) |
| 5000 | TCP | Keystone | localhost | OpenStack authentication API |
| 8774 | TCP | Nova API | localhost | Compute API endpoint |
| 9292 | TCP | Glance API | localhost | Image service API |
| 9696 | TCP | Neutron API | localhost | Networking API endpoint |
| 6080 | TCP | noVNC | localhost | VNC console proxy for Horizon |
| 10.20.20.1 | HTTP | Horizon | Internal | MicroStack internal gateway IP |
9.4 Log File Locations
| Log File | Contents | Rotation |
|---|---|---|
/var/log/nginx/access.log |
Nginx HTTP access requests | daily (logrotate) |
/var/log/nginx/error.log |
Nginx errors and warnings | daily (logrotate) |
/var/log/mysql/error.log |
MySQL errors and startup messages | manual |
/var/log/auth.log |
SSH login attempts, sudo usage | weekly (logrotate) |
/var/log/syslog |
General system messages | daily (logrotate) |
/var/log/fail2ban.log |
Fail2ban ban/unban events | daily (logrotate) |
snap logs microstack.<service> |
OpenStack service logs | snap-managed |
/var/log/letsencrypt/letsencrypt.log |
Certbot SSL certificate operations | manual |
10. Next Steps and Roadmap
10.1 Production Architecture
When ready to move beyond development, the recommended production architecture separates the controller and compute functions across multiple droplets:
10.1.1 Phase 1: Separate LEMP and OpenStack
+----------------------------+ +----------------------------+
| LEMP Web Droplet ($12) | | OpenStack Droplet ($48) |
| 1 vCPU / 2 GB RAM | | 4 vCPU / 8 GB RAM |
| | | |
| Nginx reverse proxy | | MicroStack (all-in-one) |
| Let's Encrypt SSL | | Controller + Compute |
| Static/error pages | | VMs run here |
+----------------------------+ +----------------------------+
| |
+---------------+------------------------+
|
openstack.kuyaops.com
(A record -> LEMP droplet IP)
Total: $60/mo (vs $48/mo for combined)
Benefit: Public web services isolated from OpenStack; independent scaling
10.1.2 Phase 2: Dedicated Compute Node
+------------------------+ +---------------------------+
| Controller ($48) | | Compute Node 1 ($48) |
| 4 vCPU / 8 GB RAM | | 4 vCPU / 8 GB RAM |
| | | |
| Keystone | | Nova Compute |
| Glance | | Libvirt/KVM |
| Neutron Server | | Neutron Agent |
| Horizon | | (Runs VMs) |
| MySQL + RabbitMQ | | |
+------------------------+ +---------------------------+
|
+----------v---------------------------------------------+
| LEMP Droplet ($12) - Nginx proxy for Horizon |
+--------------------------------------------------------+
10.2 Adding More Compute Nodes
To add additional compute capacity to a MicroStack deployment:
- Provision a new Ubuntu 24.04 droplet with KVM support
- Install MicroStack:
snap install microstack --classic - Join it to the controller:
microstack init --auto --compute --join <controller-ip> - Verify the hypervisor appears:
microstack.openstack hypervisor list
10.3 Ceph Storage Backend
For production-grade persistent storage, integrate Ceph as the storage backend:
- Ceph RBD — Block storage for VM volumes (replaces Cinder LVM)
- CephFS — Shared filesystem for VM migration
- Ceph Object Gateway — S3-compatible object storage
A minimal Ceph cluster requires 3 storage nodes (OSD daemons) for data redundancy. Each node should have at least 100 GB of additional block storage.
10.4 Load Balancing
For high availability, add load balancers in front of the controller nodes:
# HAProxy configuration for OpenStack API endpoints
frontend openstack_api
bind *:5000
default_backend keystone_servers
backend keystone_servers
balance roundrobin
server controller1 10.20.20.10:5000 check
server controller2 10.20.20.11:5000 check
10.5 Monitoring with Prometheus and Grafana
Set up comprehensive monitoring to track the health and performance of your OpenStack deployment:
10.5.1 Recommended Monitoring Stack
| Component | Purpose | Exporters |
|---|---|---|
| Prometheus | Metrics collection and storage | N/A (server) |
| Grafana | Visualization and dashboards | N/A (server) |
| Node Exporter | OS-level metrics (CPU, RAM, disk, network) | 9100/tcp |
| cAdvisor | Container/snap resource usage | 8080/tcp |
| MySQL Exporter | MySQL performance metrics | 9104/tcp |
| Nginx Exporter | Nginx connection and request metrics | 9113/tcp |
| OpenStack Exporter | OpenStack API metrics (VMs, networks, quotas) | 9183/tcp |
10.5.2 Key Metrics to Monitor
# System metrics
- CPU usage and steal time
- RAM usage and swap utilization
- Disk I/O and available space
- Network throughput and packet loss
# OpenStack metrics
- Number of VMs (active, building, error)
- Hypervisor capacity (free vCPUs, RAM)
- API response times (Keystone, Nova, Neutron)
- Network packet drops
- Image upload/download rates
# Application metrics
- Nginx request rate and error rate
- MySQL query latency and connection count
- SSL certificate expiry date
10.6 Backup Strategies
10.6.1 What to Backup
| Data | Frequency | Method |
|---|---|---|
| MicroStack database | Daily | mysqldump or filesystem snapshot |
| Glance images | Weekly | rsync to offsite storage |
| VM volumes (Cinder) | Daily | Cinder backup or snapshot |
| Configuration files | On change | Git repository or S3 |
| SSH keys | On creation | Encrypted password manager |
| Credentials file | On rotation | Encrypted password manager |
10.6.2 Automated Backup Script Example
#!/bin/bash
# /root/backup_openstack.sh
# Run via cron: 0 2 * * * /root/backup_openstack.sh
BACKUP_DIR="/backups/openstack/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
# Export MicroStack configuration
snap get microstack > "$BACKUP_DIR/microstack-config.txt"
# Backup MySQL (if using external MySQL)
mysqldump -u root --all-databases > "$BACKUP_DIR/mysql-all-databases.sql" 2>/dev/null || true
# Backup Glance images
rsync -a /var/snap/microstack/common/var/lib/glance/images/ "$BACKUP_DIR/glance-images/" 2>/dev/null || true
# Compress
tar czf "$BACKUP_DIR.tar.gz" -C "$BACKUP_DIR" .
rm -rf "$BACKUP_DIR"
# Upload to DigitalOcean Spaces (S3-compatible) or offsite
# s3cmd put "$BACKUP_DIR.tar.gz" s3://my-backup-bucket/openstack/
# Keep only last 7 days
find /backups/openstack -name "*.tar.gz" -mtime +7 -delete
echo "Backup completed: $BACKUP_DIR.tar.gz"
10.7 Pre-Production Checklist
Before moving any workload to production, complete this checklist:
| Check | Status | Notes |
|---|---|---|
| Separate LEMP and OpenStack onto different droplets | [ ] | Improved security and scalability |
| Restrict SSH to specific IP addresses (UFW) | [ ] | Don't allow SSH from 0.0.0.0/0 |
| Enable UFW rate limiting for SSH | [ ] | UFW: limit 22/tcp |
| Configure HSTS header in Nginx | [ ] | Enforce HTTPS |
| Rotate all default passwords | [ ] | MicroStack admin, MySQL root |
| Set up automated security updates | [ ] | unattended-upgrades |
| Configure Prometheus + Grafana monitoring | [ ] | Track all key metrics |
| Set up automated backups | [ ] | Daily with 7-day retention |
| Configure SSL certificate expiry alerts | [ ] | Alert 30 days before expiry |
| Document disaster recovery procedures | [ ] | Test the full reset procedure |
| Set up log aggregation | [ ] | Centralized logging with rsyslog or ELK |
| Create runbooks for common operations | [ ] | VM provisioning, network changes, etc. |
10.8 Additional Resources
| Resource | URL | Description |
|---|---|---|
| OpenStack Documentation | https://docs.openstack.org | Official documentation for all services |
| MicroStack Documentation | https://microstack.run/docs | Canonical's MicroStack-specific docs |
| Ubuntu Server Guide | https://ubuntu.com/server/docs | OS-level administration |
| DigitalOcean Docs | https://docs.digitalocean.com | Droplet management and networking |
| Nginx Documentation | https://nginx.org/en/docs/ | Web server and reverse proxy |
| MySQL 8.0 Reference | https://dev.mysql.com/doc/refman/8.0/en/ | Database administration |
| PHP Documentation | https://www.php.net/docs.php | PHP runtime and configuration |
| Let's Encrypt | https://letsencrypt.org/docs/ | Free SSL certificate authority |