Everything I'd learnt about AWS so far was about AWS itself — EC2, S3, IAM. Ansible is the first tool I've touched that sits on top of servers and actually does work on them. Instead of logging into each server by hand and typing the same commands over and over, you write the steps down once and let Ansible run them for you, on as many servers as you want.

But before Ansible can do anything, it needs a way to get into your servers without you typing a password every single time. So that's where I started.

Setting the stage: two EC2s

I spun up two EC2 instances for this. One is the control node — this is where Ansible actually lives, and where I run all the commands from. The other is the target node, the machine that gets configured. Nothing fancy about either one, they're just regular EC2 instances. The interesting part is getting the first one to log into the second one without any password prompt.

Logging into EC2: browser vs MobaXterm

Quick detour before the SSH key stuff, because I used both ways to get into these machines and it's worth knowing both.

The lazy, no-setup way is straight from the AWS console. Open the EC2 page, select your instance, hit Connect, pick EC2 Instance Connect, and click Connect again. A terminal just opens right there in the browser tab — no .pem file needed, AWS handles the identity check quietly in the background. To leave, just type exit or hit Ctrl+D.

The other way I used is MobaXterm, which is pretty much the go-to SSH client if you're on Windows. Open it, go to Session → SSH, type in the instance's public IP, and set the username (ubuntu for Ubuntu boxes, ec2-user for Amazon Linux). Then under Advanced SSH Settings, tick "Use private key" and point it at your .pem file. Hit OK and you're in. Same deal to exit — just type exit.

One thing that trips people up on Linux/Mac if you're SSHing with the .pem file directly from a terminal instead of a GUI tool: SSH will flat-out refuse to use the key unless its permissions are locked down first.

chmod 400 mykey.pem ssh -i mykey.pem ubuntu@<public-ip> 

Making the SSH login passwordless

Here's the actual goal: get the control node to SSH into the target node without a password or a key prompt getting in the way. This has nothing to do with Ansible specifically — it's just how SSH key-based login normally works, Ansible is simply relying on it already being set up.

First, on the control node, I generated a new key pair:

ssh-keygen -t rsa -b 4096 

ssh-keygen is just the tool, -t rsa picks the key type, and -b 4096 sets how strong the key is. It'll ask where to save it (I just hit Enter for the default) and whether to set a passphrase — I left that blank, because the whole point is a login with zero typing involved.

This gives you two files: id_rsa, your private key (never shared, ever), and id_rsa.pub, your public key, which is the one you hand out to other machines so they recognize you.

Getting that public key onto the target machine is the part that actually makes the login passwordless. The easy way:

ssh-copy-id -i ~/.ssh/id_rsa.pub ubuntu@<target-private-ip> 

Or you can do it by hand, which honestly helped me understand what's going on better. Print the key with cat ~/.ssh/id_rsa.pub, copy the output, SSH into the target using its .pem file (you still need that the first time), then open vim ~/.ssh/authorized_keys on the target and paste the key in on a new line. Press i to type, Esc, then :x to save and quit — same vim trick from my last post.

After that, jump back to the control node and test it:

ssh ubuntu@<target-private-ip> 

If that drops you straight into a shell with no prompt at all, you're done — passwordless SSH is working, and that's exactly what Ansible needs underneath.

Installing Ansible

Ansible only goes on the control node. The target machines don't need anything installed on them at all, which is kind of the whole appeal — it's "agentless," it just talks to servers over SSH and does its thing, no background process running on the other end.

sudo apt update sudo apt install ansible -y 

or, if you'd rather go through pip:

sudo apt install python3-pip -y pip install ansible 

Check it worked with ansible --version.

Once it's in, the whole point is this: any time you need to set up or change something across a bunch of servers consistently — installing a package, copying a config file, restarting a service — you stop doing it by hand on each one and let a playbook do it everywhere at once.

Telling Ansible which servers exist: the inventory file

Ansible needs a list of the machines it's allowed to touch. That list is called an inventory, and it's about as simple as it sounds:

nano inventory.ini 
[targets] target1 ansible_host=<target-private-ip> ansible_user=ubuntu 

That's just saying: there's a group called targets, with one server in it, log in as ubuntu. Test the connection with:

ansible all -i inventory.ini -m ping 

A reply like "pong" back means Ansible can reach the server and talk to it fine.

Writing my first playbook

A playbook is just a YAML file listing out the steps you want done — step 1, step 2, step 3, like a recipe. Mine installs and starts nginx on the target:

--- - name: My first playbook hosts: targets become: yes tasks: - name: Update apt cache apt: update_cache: yes - name: Install nginx apt: name: nginx state: present - name: Start nginx service service: name: nginx state: started 

hosts: targets says which group from the inventory to run on. become: yes means run with sudo. Each task under tasks: just has a readable name and a module (apt, service) that's the actual thing getting done.

Run it with:

ansible-playbook -i inventory.ini first_playbook.yml 

Ansible connects over the passwordless SSH we set up, runs each step in order, and tells you exactly what changed on the way.

Why bother with any of this

Without it, setting up a server means SSHing in by hand and retyping the same update/install/configure/start sequence every single time — and it's easy to slip up when you're doing that across a dozen machines. Write the steps once as a playbook, and the same command works whether you're running it on one server or a hundred, with the same result every time.

Things I made sure I could explain out loud (interview-style)

What is Ansible — one line: it automates configuring and managing servers using simple text files, over SSH, with nothing extra installed on the servers themselves.

Why "agentless" — because nothing needs to run permanently on the target machines. Ansible connects over SSH, does the task, and disconnects. Tools like Puppet or Chef instead need an agent process running on every server they manage.

What idempotency means, and why Ansible cares — running the same playbook twice gives you the same end result without errors or duplicate changes. The "install nginx" task checks if it's already installed before doing anything, so running the playbook five times in a row is harmless.

Ad-hoc command vs playbook — an ad-hoc command is a single one-off instruction typed straight into the terminal, like that ping test earlier. A playbook is a saved file with multiple steps, meant to be reused.

What's an inventory — just the list of servers (and groups of servers) Ansible is allowed to manage, plus details like IP and login user.

The thing that actually clicked for me today is that passwordless SSH isn't some Ansible feature — it's plain old SSH key setup that happens to already exist, and Ansible just leans on it. Once that login works smoothly by hand, Ansible is really just typing it for you, from a file instead of a keyboard.