Chapter 4: Playbooks — The Core
Topic 24

Privilege Escalation with become

ConceptSecurity

become is how Ansible runs a task as a different user — almost always root — after connecting over SSH as an unprivileged account. You log in as deploy, then become root through sudo for the tasks that need it, because SSHing directly as root is a security posture Ansible is built to avoid. The escalation is a separate, explicit step from the connection, and that separation is the whole idea worth understanding precisely.

Keeping the login and the privilege distinct is what lets a fleet run under least access without giving up the ability to install packages and write to /etc. This topic separates the connection user from the become user, walks the three knobs — become, become_user, become_method — covers per-play versus per-task escalation, the sudo-password flag, and why direct root SSH is the wrong default.

Connect as a User, Act as Root

Ansible's SSH connection and its privilege are two different things. ansible_user: deploy decides who logs in; become: yes decides who the task then runs as. You connect as a named, low-privilege account and escalate only for the work that needs root. This split is why you never put root in your SSH config — the login stays unprivileged and the escalation is an explicit, per-the-principle-of-least-access step.

Hold those two ideas apart and most of become follows. The connection answers "how do I get onto the box," the escalation answers "as whom do I run this task once I am there." They are configured separately, logged separately, and failing one is a different problem from failing the other.

Connect low, act high, drop back
SSH in as deploy
become via sudo
task runs as root
drop back to deploy

become, become_user, become_method

Three settings control escalation. become: yes turns it on. become_user: sets the target identity — root by default, but you set it to postgres to run a task as the database owner. become_method: picks the mechanism, with sudo the default and su and others available for hosts that require them. Together they answer on, as whom, and by what means.

Play-level root for installs, per-task postgres for the DB work
- name: Configure the database tier
  hosts: db
  become: yes                  # escalate to root for most tasks
  become_method: sudo
  tasks:
    - name: Install PostgreSQL 16
      ansible.builtin.apt:
        name: postgresql-16
        state: present

    - name: Create the larkspur database
      become_user: postgres     # this task runs as the DB owner, not root
      community.postgresql.postgresql_db:
        name: larkspur
        state: present

The play escalates to root for the package install, then one task overrides become_user to postgres for the database creation, which must run as the owner of the cluster. The connection user never changes — only who each task runs as once connected. That is the three knobs working together on one tier.

Per-Play vs Per-Task

Set become: yes on the play when most tasks need root, or on a single task when only it does. The common Larkspur pattern is exactly the one above: play-level root for installing packages and writing config, plus a per-task become_user: postgres for the database tasks that must run as the cluster owner. The play sets the default; the task sets the exception.

Choosing the level is about where the privilege belongs. If a play is mostly root work, declare it once at the play level and override the few tasks that differ. If only one task in an otherwise unprivileged play needs escalation, put become on that task alone. Both read clearly; scattering play-wide privilege across every task does not.

--ask-become-pass and the sudo Password

If the deploy account's sudo requires a password, the run needs --ask-become-pass-K for short — to prompt for it once at the start. Without that flag on a password-required host, every escalated task fails at once, because sudo is waiting for a password that never comes. The alternative is sudoers config that grants the automation passwordless sudo for the specific commands it runs.

The failure mode is distinctive: not one task failing, but every escalated task failing together with a become-password error. When you see that pattern, the fix is almost always a missing -K or a sudoers entry that does not grant what the run needs. Knowing the shape of the failure turns a confusing wall of errors into a one-line fix.

Why You Don't SSH as Root

Direct root SSH is disabled on a well-run server — PermitRootLogin no — and for good reasons that become respects. Logging in as root removes the per-user audit trail, so you cannot tell who did what; it hands full control to anyone who obtains the key; and it skips the sudo log that records every escalation. Connecting as a named user and escalating keeps that log honest and the blast radius scoped to what sudo permits.

This is why become exists as a separate step rather than a convenience you can shortcut with ansible_user: root. The named-user-plus-sudo path is more secure, more auditable, and survives a server that hardens its SSH config. Reaching for root login to skip an escalation step trades all of that away for a few saved keystrokes.

Common Mistakes
  • Setting ansible_user: root and SSHing in as root to skip become — it works until the server disables root login, removes the audit trail, and turns a stolen key into total control.
  • Running a play that needs root without become: yes and getting "Permission denied" on the first apt task, then reading it as a module bug rather than a missing escalation.
  • Forgetting --ask-become-pass on a host whose sudo needs a password, so every escalated task fails at once waiting for a become password that never arrives.
  • Leaving become_user at the default root for a PostgreSQL task that must run as the postgres user, so the command runs as root and the database operation fails or leaves root-owned files.
  • Granting blanket passwordless sudo to deploy across the fleet for convenience, widening what a compromised control node can do on every host.
Best Practices
  • Connect as a named, unprivileged user like deploy and become root only for the tasks that need it, never SSHing directly as root.
  • Set become: yes at the play level for tiers where most tasks need root, and use per-task become_user: such as postgres for the exceptions.
  • Use --ask-become-pass when sudo requires a password, or scope passwordless sudo narrowly to the specific commands the automation needs rather than blanket NOPASSWD.
  • Keep become_method: sudo as the default and change it only for hosts that genuinely require su or another method, so escalation behavior stays uniform across the fleet.
  • Read an all-tasks-fail-at-once escalation error as a sudo-password or sudoers problem, not a per-task bug, and fix it at the -K or sudoers level.
Comparable tools Terraform runs against a cloud API, not a sudo boundary — no escalation step Puppet · Chef agents already run as root, so they have no escalation Bare sudo in a shell script — the unstructured version become formalizes sudoers NOPASSWD the host-side config that grants passwordless escalation

Knowledge Check

What is the difference between the connection user and the become user?

  • ansible_user decides who logs in over SSH; become decides who the task then runs as once connected — two separate steps
  • They are the same setting written under two different names, both naming the SSH login account, kept around only for backward compatibility with older playbooks
  • The connection user is who the tasks run as, and the become user only opens the initial SSH session
  • The become user logs in over SSH first, then drops down to the connection user to do the actual work

What do become_user and become_method control?

  • become_user sets the identity a task runs as (root by default, or e.g. postgres); become_method sets the mechanism, defaulting to sudo
  • become_user sets the SSH login name used to open the connection and become_method sets the TCP port the SSH transport connects on, so the pair fully configures how the control node reaches the host
  • Both keywords only control the verbosity of the escalation log, not which identity actually runs the task
  • become_user picks whether to use sudo or su, and become_method names the target account to run as

A run against a host whose sudo needs a password fails with every escalated task erroring at once. What is the likely fix?

  • Pass --ask-become-pass (-K) so the run prompts for the sudo password, or grant scoped passwordless sudo on the host
  • Switch ansible_user to root and connect directly as the superuser, dropping become from the play, so that the run never has to clear a sudo password prompt on any host
  • Add --limit to retry one host at a time until sudo caches the password across the runs
  • Lower the verbosity level, since the errors are merely cosmetic and the tasks actually ran fine

Why is connecting as a named user and escalating with become the right default over SSHing directly as root?

  • It preserves a per-user audit trail and the sudo log, scopes the blast radius, and survives a host that sets PermitRootLogin no
  • Direct root SSH is measurably slower, because root login sessions are rate-limited at the kernel level
  • A root SSH session cannot execute Ansible modules at all, because the module interpreter refuses to launch under uid 0, which makes escalating with become from a named user mandatory
  • A named-user login removes the need for any SSH key, since become handles all of the authentication

You got correct