← Back to list

Using SSH to Build a Minimal, Production-Ready Configuration Management System

Sometimes SSH + Bash is exactly the right tool.

Obafemi · 2026-01-17 16:56 · 4 claps · 3.2 min read paywalled
#configuration-file #configuration-management #ssh #bash #devops
Open on Medium ↗
Wiki topics: BIZ · Business Strategy ☁️ · DevOps & Cloud

Using SSH to Build a Minimal, Production-Ready Configuration Management System

Sometimes SSH + Bash is exactly the right tool.

DevOps engineers love powerful platforms but experience teaches us that simplicity wins when the problem allows it.

This approach is ideal for:

  • Small teams
  • Homelabs
  • Side projects
  • Early-stage startups
  • Static or slowly changing infrastructure
  • One-off or periodic automation

In this article, we’ll build a minimal but production-safe configuration management system using only SSH and Bash.

No agents. No YAML sprawl. No frameworks to babysit.

Just SSH, Bash and Git.

What Do We Mean by “Configuration Management”?

Traditional CM tools provide:

  • Idempotent execution
  • Centralized configuration
  • Repeatable server state
  • Remote execution
  • Auditing and history

We can capture most of that value with:

  • SSH for transport
  • Bash for logic
  • Git as the source of truth
  • Clear structure and conventions

This works well when:

  • You manage fewer than 50 hosts
  • Servers are long-lived
  • You value transparency over abstraction
  • You want zero runtime dependencies

Project Structure

config-mgmt/
├── inventory/
│   ├── prod
│   ├── staging
│   └── dev
├── roles/
│   ├── base/
│   │   ├── install.sh
│   │   └── configure.sh
│   ├── nginx/
│   ├── docker/
│   └── users/
├── scripts/
│   ├── run.sh
│   ├── ssh.sh
│   └── utils.sh
├── files/
│   └── nginx.conf
└── logs/

Conceptually:

  • Inventory → hosts per environment
  • Roles → reusable configuration logic
  • SSH → execution and transport
  • Git → desired state and history

Step 1: Inventory (Simple, Safe, Boring)

Each inventory file is one host per line.

inventory/prod

# Production servers
ubuntu@10.0.1.10
ubuntu@10.0.1.11
ubuntu@10.0.1.12

Rules:

  • Comments allowed
  • Blank lines allowed
  • No hidden metadata
  • Treat inventory as high-trust input

Step 2: Harden SSH for Automation

Before automating anything, fix SSH properly.

~/.ssh/config

Host *
  ServerAliveInterval 30
  ServerAliveCountMax 3
  StrictHostKeyChecking accept-new
  ControlMaster auto
  ControlPersist 10m
  ControlPath ~/.ssh/cm-%r@%h:%p
  BatchMode yes

This gives you:

  • Connection reuse ( which is a huge speed boost)
  • Stable long-running commands
  • Safe host verification
  • No hanging password prompts

Step 3: Centralized SSH Wrapper

scripts/ssh.sh

#!/usr/bin/env bash
set -euo pipefail

ssh_exec() {
  local host="$1"
  local stdin_script="${2:-}"

  if [[ -n "$stdin_script" ]]; then
    ssh "$host" "bash -s" < "$stdin_script"
  else
    ssh "$host"
  fi
}

Why this matters:

  • One place to adjust SSH behavior
  • Consistent error handling
  • Easier debugging later

Step 4: Idempotent Roles (The Golden Rule)

Every role must be safe to run repeatedly.

Example: Base System Setup

roles/base/install.sh

#!/usr/bin/env bash
set -euo pipefail

# Ensure passwordless sudo
sudo -n true

if ! command -v curl >/dev/null 2>&1; then
  sudo apt-get update -y
  sudo apt-get install -y curl
fi

if ! command -v vim >/dev/null 2>&1; then
  sudo apt-get install -y vim
fi

The rule is; if running a script twice causes damage, the script is wrong.

Step 5: Configuration Files with Validation

Avoid SCP and avoid temp files but never reload a service without validation.

roles/nginx/configure.sh

#!/usr/bin/env bash
set -euo pipefail

sudo -n true

sudo mkdir -p /etc/nginx

sudo tee /etc/nginx/nginx.conf >/dev/null <<'EOF'
user www-data;
worker_processes auto;

events {
  worker_connections 1024;
}

http {
  include       mime.types;
  default_type  application/octet-stream;
  sendfile      on;
}
EOF

sudo nginx -t
sudo systemctl reload nginx

This guarantees:

  • Atomic replacement
  • Syntax validation
  • No accidental outages

Step 6: Orchestration Script

scripts/run.sh

#!/usr/bin/env bash
set -euo pipefail

ENV="${1:?Environment required}"
ROLE="${2:?Role required}"
MAX_PARALLEL=5

INVENTORY="inventory/$ENV"
ROLE_DIR="roles/$ROLE"

[[ -f "$INVENTORY" ]] || { echo "Missing inventory: $INVENTORY"; exit 1; }
[[ -d "$ROLE_DIR" ]] || { echo "Missing role: $ROLE"; exit 1; }

failures=()
pids=()

run_host() {
  local host="$1"
  echo "==== $host ===="

  ./scripts/ssh.sh "$host" "$ROLE_DIR/install.sh"
  ./scripts/ssh.sh "$host" "$ROLE_DIR/configure.sh"

  echo "Completed on $host"
}

while IFS= read -r host; do
  [[ -z "$host" || "$host" =~ ^# ]] && continue

  run_host "$host" & pids+=($!)

  if (( ${#pids[@]} >= MAX_PARALLEL )); then
    wait -n || failures+=("$host")
  fi
done < "$INVENTORY"

wait || true

if (( ${#failures[@]} > 0 )); then
  echo "Failures: ${failures[*]}"
  exit 1
fi

This adds:

  • Safe inventory parsing
  • Parallel execution
  • Per-host isolation
  • Failure reporting
  • No single flaky host blocking progress

Step 7: Logging and Auditing

Always log executions.

./scripts/run.sh prod nginx \
  | tee logs/nginx-prod-$(date +%F-%H%M).log

You now have:

  • Execution history
  • Debug artifacts
  • Change timelines

Step 8: Git Is Your State Management

If used correctly, Git gives you:

  • Change tracking
  • Peer review
  • Rollback via revert
  • Tagged snapshots per environment

This is basically GitOps without the ceremony.

But being honest, Git represents desired state not observed runtime state.

If you have hundreds of hosts, complex dependencies or have need for drift detection, this approach is a bad idea. That’s when tools like Ansible or Salt or Puppet earn their cost.

[embed]5 Practical Bash Scripts for SSH Monitoring (No SIEM, No Agents) Simple tools used correctlymedium.com

[embed]11 hidden gems of systemd systemd is much more than “start and stop”medium.com

Go from a vanilla Ubuntu server to a production-ready baseline in under 10 minutes with the DevOps Starter Kit — a bundle of 20+ Bash scripts, cron jobs, and Docker templates to speed up your infrastructure setup.


메타데이터
post_id
cc8fa7ebe466
slug
using-ssh-to-build-a-minimal-production-ready-configuration-management-system-cc8fa7ebe466
url
https://medium.com/@obaff/using-ssh-to-build-a-minimal-production-ready-configuration-management-system-cc8fa7ebe466
canonical_url
https://medium.com/@obaff/using-ssh-to-build-a-minimal-production-ready-configuration-management-system-cc8fa7ebe466
author_url
https://medium.com/@obaff
status
ok
fetched_at
2026-07-08 10:57:59