Introduction
Ansible displays task results in terminal output, log files, and callback plugins. When tasks handle passwords, API keys, or tokens, this data appears in plain text. no_log: true suppresses the output for sensitive tasks, replacing it with "censored" — preventing secret leakage in CI/CD logs, terminal history, and centralized logging systems.
Basic Usage
---
- name: Secure task examples
hosts: all
tasks:
# WITHOUT no_log — password visible in output!
# TASK [Create user] ****
# changed: [web01] => {"name": "deploy", "password": "NOT_LOGGING_PASSWORD"...}
# WITH no_log — output censored
- name: Create application user
ansible.builtin.user:
name: deploy
password: "{{ vault_deploy_password | password_hash('sha512') }}"
state: present
no_log: true
# TASK [Create user] ****
# changed: [web01] => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result"}
Common Use Cases
Database Credentials
- name: Configure database connection
ansible.builtin.template:
src: database.yml.j2
dest: /opt/app/config/database.yml
mode: '0600'
no_log: true
vars:
db_password: "{{ vault_db_password }}"
- name: Create database user
community.postgresql.postgresql_user:
name: myapp
password: "{{ vault_db_password }}"
db: myapp_production
priv: ALL
no_log: true
API Keys and Tokens
- name: Configure API integration
ansible.builtin.uri:
url: "https://api.example.com/v1/configure"
method: POST
headers:
Authorization: "Bearer {{ vault_api_token }}"
body_format: json
body:
setting: value
no_log: true
- name: Set environment secrets
ansible.builtin.lineinfile:
path: /etc/myapp/environment
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value }}"
mode: '0600'
loop:
- { key: "API_KEY", value: "{{ vault_api_key }}" }
- { key: "SECRET_KEY", value: "{{ vault_secret_key }}" }
- { key: "DB_URL", value: "{{ vault_db_url }}" }
no_log: true
SSL Certificates and Private Keys
- name: Deploy SSL private key
ansible.builtin.copy:
content: "{{ vault_ssl_private_key }}"
dest: /etc/ssl/private/app.key
owner: root
group: ssl-cert
mode: '0640'
no_log: true
- name: Deploy SSL certificate
ansible.builtin.copy:
content: "{{ ssl_certificate }}"
dest: /etc/ssl/certs/app.crt
mode: '0644'
# no_log NOT needed — certificates are public
Conditional no_log
# Only hide in production
- name: Deploy configuration
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.conf
no_log: "{{ env_name == 'production' }}"
# Hide based on variable
- name: Set secret
ansible.builtin.command:
cmd: "vault write secret/myapp key={{ secret_value }}"
no_log: "{{ not (debug_mode | default(false)) }}"
no_log with Loops
# Hides ALL loop iterations
- name: Create multiple users
ansible.builtin.user:
name: "{{ item.name }}"
password: "{{ item.password | password_hash('sha512') }}"
loop: "{{ users }}"
no_log: true
# You lose the loop label — can't see which user is being processed
# Better: show names but hide passwords
- name: Create multiple users
ansible.builtin.user:
name: "{{ item.name }}"
password: "{{ item.password | password_hash('sha512') }}"
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
no_log: true
no_log with register
# The registered variable is also censored in debug output
- name: Get secret from vault
ansible.builtin.command:
cmd: vault kv get -field=password secret/myapp
register: secret_result
no_log: true
# This debug will show the censored message, not the actual value
- name: Try to show secret (won't work)
ansible.builtin.debug:
var: secret_result
# Output: "censored"
# But you CAN use the variable in subsequent tasks
- name: Use the secret
ansible.builtin.template:
src: config.j2
dest: /etc/myapp/config.yml
vars:
app_password: "{{ secret_result.stdout }}"
no_log: true # Also hide this task's output
Global no_log Setting
# ansible.cfg — NOT recommended for production
[defaults]
# Show no_log values (DANGEROUS — only for debugging)
display_args_to_stdout = false
no_log = false # Override all no_log directives
# Temporarily show censored output for debugging
ANSIBLE_NO_LOG=false ansible-playbook site.yml
# ⚠️ NEVER do this in CI/CD or shared environments
What no_log Hides
| Hidden | Not Hidden |
|---|---|
| Task arguments (module params) | Task name |
| Return values (stdout, result) | Task status (ok/changed/failed) |
| Loop item values | Host name |
| Registered variable content | Task duration |
| Debug output of no_log vars | Error messages (partially) |
Troubleshooting
| Issue | Solution |
|---|---|
| Can't debug a failing no_log task | Temporarily set no_log: false, fix, re-enable |
| CI/CD logs show secrets | Add no_log: true to ALL tasks handling secrets |
Ansible Lint warning no-log-password | Lint correctly detected missing no_log — add it |
no_log on debug task | Pointless — debug exists to show output; rethink approach |
| Error message leaks secret | Error output may still contain args; use failed_when to catch |
ansible-lint Integration
# ansible-lint automatically warns about missing no_log
# Rule: no-log-password
ansible-lint playbook.yml
# Example warning:
# playbook.yml:15: no-log-password: Tasks that set passwords should use no_log
Best Practices
- Always
no_logon password tasks —user,mysql_user,postgresql_user - Always
no_logon API auth tasks —uriwith Authorization headers - Always
no_logon key/cert tasks — private key deployments - Use
loop_control.label— preserve useful output while hiding secrets - Run ansible-lint — catches missing
no_logautomatically - Don't
no_logeverything — it makes debugging impossible - Pair with Vault —
no_loghides output; Vault encrypts at rest
Conclusion
no_log: true is a one-line addition that prevents secrets from leaking into terminal output, CI/CD logs, and monitoring systems. Apply it to every task that handles passwords, tokens, keys, or connection strings. Combined with Ansible Vault for encryption at rest, no_log completes the secret protection story — encrypted in files, hidden in output.