Skip to main content
· Automation · 4 min read

Automating Proxy Deployments with Bash and Ansible

The gap between a script that works on your machine and infrastructure as code is usually about parameterization, idempotency, and automation. This post walks through converting a one-off Squid proxy installation script into something you can deploy across a fleet with Ansible.

The Interactive Bash Script

The original script used whiptail to collect credentials interactively:

#!/bin/bash
# squid-install.sh

set -euo pipefail

# Get Squid credentials from whiptail dialog
CREDENTIALS=$(whiptail --title "Squid Proxy Setup" \
  --passwordbox "Enter proxy credentials (username:password)" 10 60 3>&1 1>&2 2>&3)

USERNAME=$(echo $CREDENTIALS | cut -d: -f1)
PASSWORD=$(echo $CREDENTIALS | cut -d: -f2)

# Install Squid and htpasswd
apt-get update -qq
apt-get install -y squid apache2-utils

# Create password file
htpasswd -bc /etc/squid/passwd "$USERNAME" "$PASSWORD"

# Configure Squid with basic auth
cat > /etc/squid/squid.conf <<EOF
http_port 3128
acl auth_users proxy_auth REQUIRED
http_access allow auth_users
http_access deny all
EOF

# Restart Squid
systemctl restart squid

# Open firewall
iptables -A INPUT -p tcp --dport 3128 -j ACCEPT

echo "Proxy installed and running on port 3128"

Works great on a single machine. But running this interactively on 10 servers is not sustainable.

Converting to Ansible

# squid-proxy/tasks/main.yml
- name: Install Squid and utilities
  apt:
    name:
      - squid
      - apache2-utils
    update_cache: yes
    state: present

- name: Create Squid password file
  htpasswd:
    path: /etc/squid/passwd
    name: "{{ proxy_user }}"
    password: "{{ proxy_password }}"
    create: yes
  no_log: true  # Don't log the password

- name: Configure Squid authentication
  template:
    src: squid.conf.j2
    dest: /etc/squid/squid.conf
    mode: '0644'
  notify: Restart Squid

- name: Ensure Squid is running
  service:
    name: squid
    state: started
    enabled: yes

- name: Open Squid port in firewall
  iptables:
    chain: INPUT
    protocol: tcp
    destination_port: '3128'
    jump: ACCEPT
    save: yes
# squid-proxy/templates/squid.conf.j2
http_port {{ proxy_port | default(3128) }}

acl auth_users proxy_auth REQUIRED
http_access allow auth_users
http_access deny all

# Logging
access_log /var/log/squid/access.log squid
# squid-proxy/handlers/main.yml
- name: Restart Squid
  service:
    name: squid
    state: restarted
# squid-proxy/defaults/main.yml
proxy_port: 3128
proxy_user: admin
# proxy_password should be passed via vault or CLI argument

Running the Playbook

# Run with password passed securely
ansible-playbook -i inventory.yml squid-proxy.yml \
  --extra-vars "proxy_password=S3cur3P@ssw0rd"

For production, use Ansible Vault:

# Encrypt the password file
ansible-vault encrypt_string 'S3cur3P@ssw0rd' --name proxy_password

# Run with vault
ansible-playbook -i inventory.yml squid-proxy.yml --ask-vault-pass

Architecture: Single Proxy to Fleet

%%{ init: { 'look': 'handDrawn' } }%%
graph TD
    A[Ansible Control Node] -->|Playbook + Config| B[Proxy Fleet]
    B --> C[Squid Proxy 1<br/>10.0.1.10:3128]
    B --> D[Squid Proxy 2<br/>10.0.1.11:3128]
    B --> E[Squid Proxy N<br/>10.0.1.N:3128]
    C --> F[Internet]
    D --> F
    E --> F
    G[Build agents<br/>CI runners] -->|Traffic<br/>through proxy| C
    G -->|Traffic<br/>through proxy| D
    G -->|Traffic<br/>through proxy| E

CI runners route traffic through the proxy fleet. The proxy enforces basic auth and logs access — useful for filtering outbound traffic in air-gapped or restricted environments.

Idempotency: The Key Difference

The original bash script, run twice, might create duplicate entries in the password file or restart Squid unnecessarily. The Ansible playbook:

  • htpasswd with create: yes only creates the file if it doesn’t exist
  • Template with notify: Restart Squid only restarts if the config changed
  • service: state: started ensures running but doesn’t restart if already up

Idempotency means you can run the playbook any number of times and the result is the same: a correctly configured proxy. That’s what turns a one-off script into reproducible infrastructure.

Testing the Proxy

# From a build agent
curl -x http://admin:S3cur3P@[email protected]:3128 \
  https://ifconfig.me

# Verify the IP shown is the proxy's outbound IP
# Not the agent's local IP

The proxy works when the response IP matches the proxy’s egress, not the agent’s source IP.

From Script to Infrastructure

The pattern is always the same:

  1. Write a script that works on one machine
  2. Identify the hardcoded values → make them variables
  3. Remove interactivity → use command-line arguments or variable files
  4. Add idempotency checks → test before acting
  5. Wrap in Ansible/Fabric → orchestrate across fleet

That one-off proxy install script becomes a playbook you can run against 10 servers in 30 seconds.