The no_log: true directive in Ansible prevents sensitive data — passwords, API keys, tokens, and certificates — from appearing in playbook output and log files. Without it, Ansible logs every task's input and output in plain text, which can expose secrets in CI/CD logs, terminal output, and callback plugin data.

How no_log Works

When you add no_log: true to a task, Ansible replaces the task output with "censored" in all logging destinations:

- name: Create database user with password
  community.mysql.mysql_user:
    name: app_user
    password: "{{ db_password }}"
    priv: "mydb.*:ALL"
    state: present
  no_log: true

Without no_log, the output shows:

TASK [Create database user with password] ****
changed: [db-server] => {"changed": true, "password": "s3cr3t_p@ssw0rd", ...}

With no_log: true, the output shows:

TASK [Create database user with password] ****
changed: [db-server] => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result"}

When to Use no_log

Always Use no_log For

  • Password and credential tasks: Creating users, database accounts, service credentials
  • API key operations: Registering tokens, setting up integrations
  • Certificate and private key handling: Deploying TLS certificates
  • Cloud provider credentials: AWS access keys, Azure service principals
  • Vault-encrypted variable usage: Any task that decrypts and uses vault secrets

Common Examples

User Management with Passwords

- name: Create application user
  ansible.builtin.user:
    name: deploy
    password: "{{ deploy_password | password_hash('sha512') }}"
    state: present
  no_log: true

API Token Registration

- name: Register service with API token
  ansible.builtin.uri:
    url: "https://api.example.com/register"
    method: POST
    headers:
      Authorization: "Bearer {{ api_token }}"
    body_format: json
    body:
      service: myapp
  no_log: true
  register: api_result

Deploying TLS Certificates

- name: Deploy SSL private key
  ansible.builtin.copy:
    content: "{{ ssl_private_key }}"
    dest: /etc/ssl/private/server.key
    owner: root
    group: root
    mode: '0600'
  no_log: true

Cloud Provider Credentials

- name: Configure AWS credentials
  ansible.builtin.template:
    src: aws_credentials.j2
    dest: /home/deploy/.aws/credentials
    owner: deploy
    mode: '0600'
  no_log: true

Applying no_log at Different Levels

Task Level (Most Common)

- name: Task with sensitive data
  ansible.builtin.debug:
    msg: "{{ secret_variable }}"
  no_log: true

Block Level

Apply to multiple tasks at once:

- block:
    - name: Create database
      community.mysql.mysql_db:
        name: myapp
        state: present

    - name: Create database user
      community.mysql.mysql_user:
        name: app_user
        password: "{{ db_password }}"
        priv: "mydb.*:ALL"
        state: present

    - name: Grant additional privileges
      community.mysql.mysql_query:
        query: "GRANT REPLICATION SLAVE ON *.* TO 'app_user'@'%'"
  no_log: true

Play Level

Hide all tasks in an entire play:

- hosts: secrets_servers
  no_log: true
  tasks:
    - name: Deploy all secrets
      ansible.builtin.copy:
        src: "{{ item }}"
        dest: "/etc/secrets/{{ item | basename }}"
      loop: "{{ secret_files }}"

Role Level (via Role Defaults)

Set no_log as a role variable:

# roles/deploy_certs/defaults/main.yml
deploy_certs_no_log: true

# roles/deploy_certs/tasks/main.yml
- name: Deploy certificate
  ansible.builtin.copy:
    content: "{{ cert_content }}"
    dest: "{{ cert_path }}"
  no_log: "{{ deploy_certs_no_log }}"

Conditional no_log

Use a variable to toggle no_log for debugging:

# In group_vars or command line
# ansible-playbook site.yml -e "show_sensitive=true"

- name: Create service account
  ansible.builtin.user:
    name: service_account
    password: "{{ svc_password | password_hash('sha512') }}"
  no_log: "{{ not (show_sensitive | default(false) | bool) }}"

This lets you temporarily disable no_log during development while keeping it enabled in production.

Debugging no_log Tasks

When a task with no_log: true fails, debugging is difficult because the error message is censored. Strategies:

1. Temporarily Disable no_log

# For debugging only — NEVER commit this change
- name: Create user
  ansible.builtin.user:
    name: app_user
    password: "{{ password }}"
  # no_log: true  # Temporarily commented out

2. Use the Conditional Approach

- name: Create user
  ansible.builtin.user:
    name: app_user
    password: "{{ password }}"
  no_log: "{{ ansible_verbosity < 3 }}"

With -vvv verbosity, no_log is disabled. With normal verbosity, it's enabled.

3. Check Variables Separately

- name: Verify password variable is defined
  ansible.builtin.assert:
    that:
      - db_password is defined
      - db_password | length > 0
    fail_msg: "db_password is not set"

- name: Create database user
  community.mysql.mysql_user:
    name: app_user
    password: "{{ db_password }}"
  no_log: true

4. Use the Ansible Debugger

- name: Create user with debugger
  ansible.builtin.user:
    name: app_user
    password: "{{ password }}"
  no_log: true
  debugger: on_failed

When the task fails, the debugger opens and you can inspect variables interactively.

Combining no_log with Ansible Vault

For maximum security, combine no_log with Ansible Vault encrypted variables:

# Encrypt the variables file
# ansible-vault encrypt group_vars/production/secrets.yml

# group_vars/production/secrets.yml (encrypted)
db_password: "s3cr3t_p@ssw0rd"
api_token: "tok_abc123def456"

# playbook.yml
- name: Deploy application
  hosts: production
  tasks:
    - name: Configure database connection
      ansible.builtin.template:
        src: db_config.j2
        dest: /etc/myapp/database.yml
      no_log: true

Why both? Vault encrypts secrets at rest (in files). no_log protects them at runtime (in output). Together, they provide defense in depth.

Common Mistakes

1. Forgetting no_log on Registered Variables

# BAD — password exposed in later debug task
- name: Get API token
  ansible.builtin.uri:
    url: https://api.example.com/auth
    method: POST
    body: '{"password": "{{ api_password }}"}'
  register: auth_result

- name: Show result
  ansible.builtin.debug:
    var: auth_result  # Exposes the password in the request body!
# GOOD
- name: Get API token
  ansible.builtin.uri:
    url: https://api.example.com/auth
    method: POST
    body: '{"password": "{{ api_password }}"}'
  register: auth_result
  no_log: true

- name: Show only the token
  ansible.builtin.debug:
    msg: "Token starts with: {{ auth_result.json.token[:4] }}..."

2. Using no_log on Non-Sensitive Tasks

Don't overuse no_log — it makes debugging harder. Only apply it to tasks that actually handle sensitive data.

3. Ignoring Callback Plugins

Some callback plugins (like ARA) store task results. no_log affects these too — the censored output is what gets stored. If you use ARA or similar tools, verify that sensitive data is indeed hidden in the recorded results.

ansible-lint Rule

The ansible-lint tool includes rule no-log-password that warns when tasks using password-related parameters lack no_log:

$ ansible-lint playbook.yml
WARNING  Listing 1 violation(s) that are fatal
no-log-password: password parameter should have no_log
playbook.yml:5 Task/Handler: Create database user

Conclusion

The no_log directive is a critical security control for any Ansible automation that handles sensitive data. Apply it consistently to tasks involving passwords, API keys, tokens, and certificates. Combine it with Ansible Vault for defense in depth, use conditional no_log for debugging flexibility, and rely on ansible-lint to catch missing no_log directives in your CI/CD pipeline.