SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

ansible-playbook flags: -k vs -K, -e, -l, -t

Why -k and -K ask for different passwords, and why -e passes false as a string. Plus -l, --tags, --list-tasks and --start-at-task, for ansible-core 2.21.5.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, October 30, 2026.

The ansible-playbook flags you use, grouped by job

The ansible-playbook flags you use most each do one job. They supply a password, pass a variable, pick which hosts or tasks run, preview a run, or change the output. ansible-playbook --help lists them in alphabetical order, which hides that grouping. This page groups them by job, so you can keep it open beside the help text and find the flag for the problem in front of you.

Everything here is written for ansible-core 2.21.5, the latest stable release on PyPI as of 30 October 2026. Most flags have kept their meaning for years. Two things behind them have changed in recent core releases: how -k handles the SSH password, and how a condition treats a string. Both sections below say which version they describe.

Set up a tiny lab to test each flag

The first-playbook Ansible tutorial installs Ansible with pipx. Do the same here, but install ansible-core and pin the version so your results match this page. ansible-core is the engine plus the ansible.builtin modules, and that is all these examples use.

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install ansible-core==2.21.5

Open a new shell so the PATH change from pipx ensurepath takes effect. Then check the version:

ansible-playbook --version

The first line should contain 2.21.5. ansible-core 2.21 needs Python 3.12 to 3.14 on the control node. If the pipx install fails, check python3 --version first.

Now make a working directory with one inventory file. It holds a single host, localhost, reached with the local connection. Ansible then runs tasks on the machine you type on and needs no SSH (Secure Shell) login.

mkdir -p ~/flags-lab && cd ~/flags-lab

Save this as inventory.ini:

[lab]
localhost ansible_connection=local

The guide to the Ansible inventory file covers groups and host variables in depth. Here you only need the lab group.

Save this two-play playbook as site.yml:

- name: Base setup
  hosts: lab
  gather_facts: false
  tags: [base]
  tasks:
    - name: Show the package list
      ansible.builtin.debug:
        msg: "Would install {{ packages | default('curl') }}"
      tags: [packages]

    - name: Show the user
      ansible.builtin.debug:
        msg: "Deploy user is {{ deploy_user | default('deploy') }}"
      tags: [users]

- name: Web tier
  hosts: lab
  gather_facts: false
  tags: [web]
  tasks:
    - name: Show the web port
      ansible.builtin.debug:
        msg: "nginx listens on {{ web_port | default(80) }}"
      tags: [nginx]

    - name: Maintenance banner
      ansible.builtin.debug:
        msg: "Maintenance mode is on"
      when: maintenance | default(false) | bool
      tags: [maintenance]

Every task is ansible.builtin.debug, which only prints a message. You can try any flag on this page without changing your machine. gather_facts: false skips the fact-gathering step and keeps the output short. The tags sit at two levels on purpose. A tag on a play is inherited by every task in that play, so base and web select whole plays. The task tags packages, users, nginx and maintenance select single tasks.

Run it once with no extra flags:

ansible-playbook -i inventory.ini site.yml

You should see both plays and a PLAY RECAP line for localhost. The maintenance task is skipped because maintenance is not defined, so it falls back to false.

-k vs -K vs -b: which password flag do you need?

These three flags get mixed up because their letters are close. They answer different questions.

  • -k, or --ask-pass, asks for the connection password. That is the SSH login password of the remote user.
  • -K, or --ask-become-pass, asks for the privilege escalation password. Usually that is the sudo password.
  • -b, or --become, runs tasks with privilege escalation. The help text says it "does not imply password prompting".

So -b turns sudo on, and -K supplies its password. If sudo on the target asks for a password, -b alone has nothing to give it, so you add -K. If the remote user has passwordless sudo, -b is enough.

-k and -K both stop and wait for you to type, so run them yourself against a real server. Point hosts.ini at your VPS and put it in a [lab] group, so site.yml finds it:

ansible-playbook -i hosts.ini site.yml -u deploy -k
ansible-playbook -i hosts.ini site.yml -u deploy -b -K
ansible-playbook -i hosts.ini site.yml -u deploy -k -b -K

The first line asks for the SSH password. The second logs in with your SSH key and asks for the sudo password. The third asks for both.

-k hands a password to SSH, but it does not switch off key logins. SSH still offers your keys, and if the server accepts one, the password you typed is never checked. We ran a playbook on ansible-core 2.21 with -k and a deliberately wrong password against a VPS where the deploy user also had a key. It logged in and ended with failed=0. So a successful -k run does not prove that password login works on that server.

Does -k need sshpass?

Older guides say -k needs the sshpass program. That was true before ansible-core 2.19. In 2.19 the SSH connection plugin gained a password_mechanism option. In 2.21.5 its default is ssh_askpass, which does not use sshpass. You need sshpass only if you set the mechanism to sshpass. You can set it with the ansible_ssh_password_mechanism variable, the ANSIBLE_SSH_PASSWORD_MECHANISM environment variable, or password_mechanism under [ssh_connection] in ansible.cfg. With that setting and no program installed, the run stops with this message:

to use the password_mechanism=sshpass, you must install the sshpass program

Run ansible-doc -t connection ssh and read the password_mechanism entry to see what your installed version supports.

Use password logins as a fallback. The Ansible docs encourage SSH keys over --ask-pass. For an unattended run that still needs a become password, store the password with Ansible Vault encrypted secrets instead of typing it each time.

How do you pass variables with -e?

-e, or --extra-vars, sets variables from the command line. It takes three forms. You can give -e more than once in a single command.

The key=value form

ansible-playbook -i inventory.ini site.yml -e web_port=8080 -e deploy_user=ops

The web task should now print nginx listens on 8080, and the user task should print Deploy user is ops.

The JSON form

ansible-playbook -i inventory.ini site.yml -e '{"web_port": 8080, "packages": ["curl", "git"]}'

JSON (JavaScript Object Notation) on the command line goes inside single quotes. The docs warn that you must escape quotes and special characters for both JSON and your shell. Single quotes on the outside stop the shell from touching the double quotes inside, which removes most of that work.

The @file form

Save this as vars.yml:

web_port: 8443
maintenance: true

Then pass the file with an @ in front of its name:

ansible-playbook -i inventory.ini site.yml -e @vars.yml

The @ prefix tells Ansible to read a file. It works for YAML and JSON files. With maintenance: true in the file, the maintenance task now runs.

The real difference between the forms is the type of the value. The variables guide states it plainly: values passed with the key=value syntax are interpreted as strings. To pass a boolean, an integer, a float or a list, use JSON or a file.

What does -e flag=false do in a when condition?

This is where the string rule matters. -e maintenance=false does not pass the boolean false. It passes the five-letter string "false". A when: clause is a raw Jinja2 expression, and a string is not a boolean.

Make a small test playbook with a bare when: and save it as flag-test.yml:

- name: Test a string in a condition
  hosts: lab
  gather_facts: false
  tasks:
    - name: Runs only when flag is true
      when: flag
      ansible.builtin.debug:
        msg: "flag counted as true"

Run it with a key=value false:

ansible-playbook -i inventory.ini flag-test.yml -e flag=false

Read what your run prints, because this behaviour depends on the core version. Since 2.19, ansible-core checks that a condition produces a real boolean. In 2.21.5 the ALLOW_BROKEN_CONDITIONALS setting defaults to false. Its description says that by default, non-boolean conditionals result in an error. The 2.19 porting guide shows this error in the following shape, for a condition that produced the string localhost:

Conditional result was 'localhost' of type 'str', which evaluates to True. Conditionals must have a boolean result.

There are two clean fixes. Both make the intent explicit.

  • Pass a real boolean with JSON: -e '{"flag": false}'.
  • Convert the string in the playbook: when: flag | bool. The bool filter returns false for no, off, false and 0, matched without regard to case.

Do not turn on ALLOW_BROKEN_CONDITIONALS to silence the error. Its own description warns that non-boolean results often come from a condition that is always true. Turning it on changes the error into a deprecation warning and lets the non-boolean value through.

This is why site.yml writes when: maintenance | default(false) | bool. The task stays off unless you pass -e maintenance=true, -e '{"maintenance": true}' or a vars file that sets it.

Run only some hosts or tasks: -l, -t and --skip-tags

-l, or --limit, narrows the hosts each play targets to an additional pattern. The pattern is matched against your inventory. It narrows the play's hosts: selection and never widens it.

ansible-playbook -i inventory.ini site.yml -l localhost
ansible-playbook -i hosts.ini site.yml -l 'lab:!db1'
ansible-playbook -i hosts.ini site.yml -l @failed-hosts.txt

Put a pattern that contains ! in single quotes. The Ansible patterns guide says single quotes must be used to prevent bash interpolation. The @ form reads the host list from a file.

-t, or --tags, runs only plays and tasks that carry the tags you name. --skip-tags runs only those that do not. You can repeat either flag.

ansible-playbook -i inventory.ini site.yml -t nginx
ansible-playbook -i inventory.ini site.yml -t base
ansible-playbook -i inventory.ini site.yml --skip-tags users
ansible-playbook -i inventory.ini site.yml -t base --skip-tags users

-t nginx runs only the web-port task. -t base runs both tasks of the first play, because each task inherits the play's tag. --skip-tags users runs every task except the user task. On the last line, the tags guide is explicit: if you specify both --tags and --skip-tags, the latter has precedence. That line therefore runs only the package task.

Ansible reserves five tag names: always, never, tagged, untagged and all. A task tagged always runs on every run unless you pass --skip-tags always. A task tagged never is skipped unless you ask for it with --tags never or another tag the task carries.

Preview a run: --list-hosts, --list-tasks and --list-tags

ansible-playbook -i inventory.ini site.yml --list-hosts
ansible-playbook -i inventory.ini site.yml --list-tasks
ansible-playbook -i inventory.ini site.yml --list-tags

--list-hosts prints the hosts that match and runs nothing else. --list-tasks lists the tasks that would run. --list-tags lists every tag the playbook defines. They combine with the selectors above, which makes them the safe way to test a selector before a real run. Add -t nginx --list-tasks to see what a tag picks, or -l 'lab:!db1' --list-hosts to see which hosts a limit leaves.

The tags guide names one limit. These flags cannot show tags or tasks inside dynamically included files or roles, so a playbook built on include_tasks or include_role can run more than the list shows.

Check a playbook before you run it: --syntax-check

ansible-playbook -i inventory.ini site.yml --syntax-check
echo $?

--syntax-check checks the playbook without running it. An exit status of 0 from echo $? means the check passed. It reads the playbook, not your hosts, so it cannot tell you whether a task will succeed on a server. To see what a failure looks like, delete the colon after one name key in a copy of site.yml, run the check on the copy, and read which line Ansible points at. A syntax check is a cheap first step in CI (continuous integration) before any run that touches a server.

Resume or walk through a run: --start-at-task and --step

--start-at-task starts the playbook at the task with the name you give:

ansible-playbook -i inventory.ini site.yml --start-at-task "Show the web port"

The name is the task's name: value. Copy it from --list-tasks instead of typing it from memory. If the play gathers facts or validates arguments, those implicit steps still run first. The flag does not work with tasks inside dynamically reused roles or tasks, the include_* family. Here the named task sits in the second play. Read which play headers your run prints before you depend on this behaviour on a real fleet.

--step stops before each task and asks whether to run it. The docs show the prompt in this form:

Perform task: configure ssh (y/n/c):

Answer y to run the task and n to skip it. Answer c to leave step mode and run all remaining tasks without asking. It waits on your keyboard, so try it yourself:

ansible-playbook -i inventory.ini site.yml --step

Dry runs and diffs: -C and -D

-C, or --check, makes no changes. Instead it tries to predict some of the changes that may occur. -D, or --diff, shows the differences when a task changes small files and templates, and the help text says it works well with --check. Every task in this lab is debug, which changes nothing, so the lab cannot show either flag in a useful way. Ansible check mode and --diff dry runs covers both against real files.

Get more output: -v

-v prints more detail, and each extra v raises the level. The built-in plugins read up to -vvvvvv. The help text suggests starting at -vvv, and says connection debugging might need -vvvv. To print a command's own output, or to keep debug messages hidden on normal runs, see how to show command output in Ansible.

Why is there no --no-color flag in ansible-playbook?

ansible-playbook --help has no colour option. Colour is a configuration setting. In ansible-core 2.21.5 that setting is ANSIBLE_NOCOLOR, and you can turn it on in any of these ways:

  • the ANSIBLE_NOCOLOR environment variable
  • the NO_COLOR environment variable, a convention many CLI tools share, which Ansible has read since 2.11
  • nocolor in the [defaults] section of ansible.cfg
ANSIBLE_NOCOLOR=1 ansible-playbook -i inventory.ini site.yml
NO_COLOR=1 ansible-playbook -i inventory.ini site.yml
[defaults]
nocolor = True

Often you need none of these. The 2.21.5 source decides colour in a fixed order. If nocolor is set, colour is off. Otherwise, if standard output is not a terminal, colour is also off. That means ansible-playbook ... > run.log and ansible-playbook ... | tee run.log both write plain text with no setting at all, because standard output is then a file or a pipe. On a real terminal, Ansible asks the curses library how many colours the terminal supports. If curses cannot find the terminal type, colour is off. Last, ANSIBLE_FORCE_COLOR overrides every rule above and turns colour back on. You want that on a CI system that shows colour codes but runs without a terminal.

To see what your run actually prints, pipe it through cat -v. It prints escape codes as visible text that starts with ^[:

ansible-playbook -i inventory.ini site.yml | cat -v
ANSIBLE_FORCE_COLOR=1 ansible-playbook -i inventory.ini site.yml | cat -v

By the order above, the first command should print plain text, because the pipe is not a terminal. The second should show ^[ codes, because force colour wins over the pipe.

FAQ

Does ansible-playbook -k need sshpass?

Not on ansible-core 2.19 or later with default settings. In 2.21.5 the SSH connection plugin's password_mechanism option defaults to ssh_askpass, and only the sshpass mechanism needs the sshpass program. On a core older than 2.19, install sshpass before you use -k. Run ansible-doc -t connection ssh to see the options your installed version has.

Why does -e flag=false not switch my task off?

Extra vars given as key=value are always strings, so flag holds the text "false" and not the boolean. Since ansible-core 2.19 a condition must produce a real boolean, and by default a non-boolean result is an error. Pass -e '{"flag": false}' to send a real boolean, or write when: flag | bool so Ansible converts the string.

What is the difference between -b and -K in ansible-playbook?

-b (--become) turns on privilege escalation, usually sudo, for the run. It does not ask for a password. -K (--ask-become-pass) asks for the password that sudo will need. Use -b alone when the remote user has passwordless sudo, and use -b -K when sudo asks for a password.

How do I turn off colour in ansible-playbook output?

There is no --no-color flag. Set ANSIBLE_NOCOLOR=1 or NO_COLOR=1 in the environment, or put nocolor = True under [defaults] in ansible.cfg. Redirecting or piping the output already turns colour off, because Ansible disables colour when standard output is not a terminal. The exception is when ANSIBLE_FORCE_COLOR is set.

Was this article useful?