🔐 Building a Linux Web Server with Terraform & Ansible – Part 2: Creating a Sudo User

Now that we’ve provisioned our droplet with Terraform, it’s time to create a proper admin user. It’s bad practice to SSH into a server as root, so we’ll create a limited user with sudo privileges for day-to-day admin tasks.

We’ll use Ansible to automate this step, just like a real production environment.


📁 Step 1: Set Up the Ansible Directory

Next to your terraform folder, create an ansible folder and move into it:

mkdir ansible && cd ansible

Create a file named create-sudo-user.yml:


⚙️ Step 2: Create the Admin User Playbook

We’ll create an Ansible playbook to:

  1. Ensure the sudo group exists
  2. Allow passwordless sudo for that group
  3. Create an admin user and assign it to the group
  4. Set up SSH access for the user

# create-sudo-user.yml
---
- hosts: all
  gather_facts: false
  tasks:
    - name: Make sure we have a 'sudo' group
      group:
        name: sudo
        state: present

    - name: Allow 'sudo' group to have passwordless sudo
      lineinfile:
        dest: /etc/sudoers
        state: present
        regexp: '^%sudo'
        line: '%sudo ALL=(ALL) NOPASSWD: ALL'
        validate: 'visudo -cf %s'

    - name: Create admin user
      user:
        name: admin
        state: present
        shell: /bin/bash
        create_home: yes
        groups: sudo
        append: yes

    - name: Create SSH directory for admin user
      file:
        path: "/home/admin/.ssh"
        state: directory
        mode: "0700"
        owner: admin
        group: admin

    - name: Add SSH key for admin
      authorized_key:
        user: admin
        state: present
        key: "{{ lookup('file', '~/.ssh/tutorial.pub') }}"

🧩 Breaking it down

Create the sudo group

- name: Make sure we have a 'sudo' group
  group:
    name: sudo
    state: present

We make sure the sudo group exists. On most Debian-based systems (like Ubuntu), this is the group used for granting admin privileges.


🔐 Allow passwordless sudo

- name: Allow 'sudo' group to have passwordless sudo
  lineinfile:
    dest: /etc/sudoers
    state: present
    regexp: '^%sudo'
    line: '%sudo ALL=(ALL) NOPASSWD: ALL'
    validate: 'visudo -cf %s'

This modifies the /etc/sudoers file to allow any user in the sudo group to run commands without being prompted for a password. The validate line ensures we don’t break sudo permissions by mistake—it checks the file before applying the change.


👤 Create the admin user

- name: Create admin user
  user:
    name: admin
    state: present
    shell: /bin/bash
    create_home: yes
    groups: sudo
    append: yes

This creates our admin user and adds them to the sudo group. We also ensure they get a home directory and use Bash as their default shell.


📁 Set up the .ssh directory

- name: Create SSH directory for admin user
  file:
    path: "/home/admin/.ssh"
    state: directory
    mode: "0700"
    owner: admin
    group: admin

We prepare the SSH directory with proper permissions so the public key can be added securely.


🔑 Add your public key

- name: Add SSH key for admin
  authorized_key:
    user: admin
    state: present
    key: "{{ lookup('file', '~/.ssh/tutorial.pub') }}"

This copies your public key to the server so that you can SSH in as the admin user. Make sure ~/.ssh/tutorial.pub exists on your local machine—it should be the same key you used with Terraform.


🧭 Step 3: Point Ansible to the Droplet

Create an inventory file with your droplet’s IP:

echo "<your-droplet-ip>" > inventory

🚀 Step 4: Run the Playbook

You can now apply the Ansible configuration like this:

ansible-playbook -i inventory create-sudo-user.yml -u root

Or, create a handy alias to shorten the command:

echo 'alias play="ansible-playbook -i inventory" >> ~/.zshrc
source .zshrc
play create-sudo-user.yml -u root

✅ Done!

You’ve now created a secure, limited admin user with passwordless sudo access and your SSH key added for login.


🔜 Next Up: Security Hardening & Ansible Roles

In the next post, we’ll:

  • Set up some essential security best practices (fail2ban, unattended upgrades)
  • Refactor our Ansible code using roles for better organization

Stay tuned 👋

Leave a Reply

Your email address will not be published. Required fields are marked *