From Control to Control: Configuring Passwordless SSH and Ansible for Remote Automation

Joel ThompsonJoel Thompson
7 min read

Introduction

This technical write-up is designed to guide you step-by-step through the process of setting up a secure, passwordless SSH connection between two EC2 instances—referred to as the Control-Host and the Work-station—and using Ansible to automate remote configuration tasks.

By the end of this guide, you'll have established key-based authentication, installed and configured Ansible, written your first playbooks, and used them to install and verify Git and Nginx on the managed host.

Whether you're a beginner looking to break into infrastructure automation or a cloud engineer seeking a practical deployment pattern, this hands-on tutorial provides both foundational knowledge and immediate, testable outcomes using modern DevOps tools.

Initial Prerequisites

  1. Create Two EC2 Instances

    • Use the same region and VPC for easier connectivity.

    • Choose Ubuntu (preferred) or any Debian-based Linux AMI.

    • Make sure each instance has a public and private IP.

  2. Name the Instances (optional but helpful)

    • Control-Host: This will run Ansible and control remote configurations.

    • Work-station: This is the target machine to be managed.

  3. Allow SSH Access (Port 22)

    • Edit each instance’s Security Group to allow inbound TCP on port 22 from your IP or 0.0.0.0/0 during testing.
  4. Create and Download a Key Pair (PEM file)

    • This key is used to connect to the EC2s initially.

    • Keep it safe and never share it.

  5. Connect to Each EC2 Instance

    • Open two terminals: one for Control-Host, the other for Work-station.

    • Use your PEM key to SSH in:
      ssh -i your-key.pem ubuntu@<public-ip>

Once these basics are in place, you're ready to:

  • Create the ansible user on both machines

  • Configure SSH key-based access

  • Install Ansible

  • Begin managing with playbooks

  1. Below is each step from your write-up, paired with why it matters. No steps have been skipped or altered—this guide will help your readers understand not just how, but why.


    1. Create the “ansible” user

    Commands (on both Control-Host and Work-station):

     sudo useradd -m -s /bin/bash ansible
     sudo usermod -aG sudo ansible
     sudo passwd ansible
    

    Why this matters:

    • Isolation & safety: Running Ansible under its own account limits blast radius if something goes wrong, instead of using root for everything.

    • Least-privilege: Adding ansible to the sudo group grants only the privileges you intend, rather than full root access by default.

    • Account consistency: Same username on control and managed hosts simplifies inventory and playbooks.


2. Switch to the ansible user

Command (on both hosts):

    sudo su - ansible

Why this matters:

  • Environment consistency: Ensures you’re working in the ansible user’s home, with the right shell and home directory (~), avoiding file-permission gotchas.

  • Avoid “works-on-my-machine” bugs: Testing as the ansible user means everything you do reflects the exact privileges Ansible will use later.


3. Generate SSH key pair on Control-Host

Command (as ansible on Control-Host):

    ssh-keygen -t rsa -b 4096
    # Press Enter through all prompts

Why this matters:

  • Secure authentication: A 4096-bit key is strong enough for most production use, resisting brute-force.

  • Passwordless automation: Later Ansible runs won’t hang waiting for passwords, enabling fully unattended operation.


4. Create the .ssh directory on Work-station

Commands (as ansible on Work-station):

    mkdir -p ~/.ssh
    chmod 700 ~/.ssh
    touch ~/.ssh/authorized_keys
    chmod 600 ~/.ssh/authorized_keys
    chown -R ansible:ansible ~/.ssh

Why this matters:

  • SSH requirements: OpenSSH insists on strict permissions; otherwise it will refuse to use the key.

  • Ownership: Ensures only the ansible user can read/write the key files, keeping them private.


5. Copy Control-Host public key

Command (on Control-Host):

    cat ~/.ssh/id_rsa.pub

Why this matters:

  • Key exchange prep: You need the public key’s text to authorize it on the managed host—this is the handshake that later allows passwordless logins.

6. Paste the public key into Work-station

Commands (on Work-station):

    vi ~/.ssh/authorized_keys
    # Paste the id_rsa.pub text, then save & exit
    cat ~/.ssh/authorized_keys

Why this matters:

  • Trust establishment: Storing the Control-Host’s public key in authorized_keys explicitly tells SSH “I trust connections from that key.”

  • Verification: A quick cat confirms you didn’t introduce whitespace or missing characters.


7. Fix permissions on Work-station (again)

Commands (on Work-station):

    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    chown -R ansible:ansible ~/.ssh

Why this matters:

  • Re-validation: After editing, reapplying permissions guarantees OpenSSH will actually load your authorized_keys file.

  • Prevents subtle SSH errors: Incorrect perms often manifest as “Permission denied” without an obvious cause.


8. Test passwordless SSH

Command (from Control-Host as ansible):

    ssh ansible@<work-station-private-IP>

Why this matters:

  • Proof of concept: Validates your key-based setup before moving on. If it still prompts for a password, fix it now rather than midway through playbook runs.

9. Install Ansible on Control-Host

Commands (as ansible or sudo):

    sudo apt update && sudo apt upgrade -y
    sudo apt install -y software-properties-common
    sudo add-apt-repository --yes --update ppa:ansible/ansible
    sudo apt install -y ansible

Why this matters:

  • Fresh package lists: Ensures you install the latest security patches.

  • Official PPA: Gives you a current Ansible release instead of the often-outdated distro version.

  • Dependency readiness: software-properties-common provides add-apt-repository.


10. Set Up the Ansible Inventory File

Commands (on Control-Host):

    sudo mkdir -p /etc/ansible
    sudo vi /etc/ansible/hosts

Add inside hosts:

    [web]
    54.82.207.91 ansible_user=ansible

Why this matters:

  • Defining targets: Inventory tells Ansible which machines to manage and which user to SSH as.

  • Group logic: Using [web] lets you later target that group in playbooks, scaling across many servers.


11. Test the Ansible Connection

Command (on Control-Host):

    ansible all -m ping

Why this matters:

  • Connectivity check: “Ping” uses SSH + a tiny Python module to confirm Ansible can actually reach and talk to your host.

  • Fail fast: If something’s mis-configured, you discover it now rather than after writing complex playbooks.


12. Allow Passwordless sudo for ansible User on Work-station

Commands (SSH into Work-station as ansible):

    sudo visudo
    # Add at end:
    ansible ALL=(ALL) NOPASSWD:ALL

Why this matters:

  • Seamless privilege escalation: Many system tasks (install packages, edit services) need root. NOPASSWD prevents Ansible from stalling on interactive sudo prompts.

13. Create the Git Playbook

On Control-Host as ansible:

    vi install_git.yml

Contents:

    ---
    - name: Install Git on managed-host
      hosts: web
      become: yes
      tasks:
        - name: Ensure Git is installed
          apt:
            name: git
            state: present
            update_cache: yes

Why this matters:

  • Declarative automation: Encodes “install Git” so you can reuse across servers, environments, or teams.

  • Idempotence: Ansible’s apt module won’t reinstall if Git is already present.


14. Run the Git Playbook

Command (on Control-Host):

    ansible-playbook install_git.yml

Why this matters:

  • Automation in action: Shows readers how to actually invoke a playbook, kick-starting the move away from manual SSH-and-apt installs.


15. Verify on Work-station

Command (SSH into Work-station):

    git --version

Why this matters:

  • Post-run validation: Confirms the playbook did what it said it would, building confidence in your automation.


16. Create the Nginx Playbook

On Control-Host:

    vi install_nginx.yml

Contents:

    ---
    - name: Install Nginx on managed-host
      hosts: web
      become: yes
      tasks:
        - name: Update APT cache
          apt:
            update_cache: yes
        - name: Install Nginx
          apt:
            name: nginx
            state: present
        - name: Ensure Nginx is running and enabled
          service:
            name: nginx
            state: started
            enabled: yes

Why this matters:

  • Full service lifecycle: Not just install, but also start and enable Nginx—ensures it survives reboots automatically.

17. Run the Nginx Playbook

Command (on Control-Host):

    ansible-playbook install_nginx.yml

Why this matters:

  • Repeatable deployment: Any new server in your [web] group can be brought up with a single command.


18. Check in the Browser

Point your browser at:

    http://<workstation-public-ip>

Why this matters:

  • User-facing test: Verifies externally that Nginx is serving pages, not just that the service is running on the host.


19. Troubleshooting if Site Can’t Be Reached

  1. Check if Nginx is running:
    sudo systemctl status nginx
  1. Verify EC2 Inbound Rules in AWS console:
  • HTTP, TCP port 80 open to 0.0.0.0/0
  1. Local test on Work-station:
    curl http://localhost

Why these matter:

  • Service health: systemctl tells you if Nginx actually started or errored out.

  • Network path validation: Security groups are a common blocker—make sure AWS isn’t dropping your traffic.

  • Host-level confirmation: curl localhost isolates whether the problem is the web server itself or the network.


Conclusion

You’ve journeyed from zero to a repeatable, secure Ansible workflow:

  1. Dedicated ansible user
    Isolated privileges and a consistent account across hosts.

  2. SSH key-based auth
    Strong, passwordless access that allows unattended automation.

  3. Ansible control node
    Up-to-date packages, official PPA, and a simple inventory.

  4. Idempotent playbooks
    Git and Nginx installs that you can run over and over without fear.

  5. Verification & troubleshooting
    Built-in checks—from ansible all -m ping to browser tests—so you know exactly where things stand.

By understanding why each permission tweak, repository add, and service enablement is critical, you’ve gained operational muscle, not just a copy-and-paste recipe.


0
Subscribe to my newsletter

Read articles from Joel Thompson directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Joel Thompson
Joel Thompson