Fix Ansible "Failed to create temporary directory" on Remote Host

intermediate๐Ÿ”ง Ansible2026-05-07| Ansible 2.9+, Linux remote hosts (Ubuntu, CentOS, RHEL, Debian), SSH-based connections

Error Message

Failed to create temporary directory. In some cases, you may have been able to authenticate and did not have permissions on the target directory. Please make sure your login shell is set correctly for your user and the environment is configured properly.
#ansible#tmp#permissions#remote#ssh

The Error

fatal: [web01]: FAILED! => {
    "msg": "Failed to create temporary directory. In some cases, you may have been able to authenticate and did not have permissions on the target directory. Please make sure your login shell is set correctly for your user and the environment is configured properly."
}

This one hits right after SSH connects successfully โ€” Ansible authenticated fine but then immediately blew up trying to create a temp dir before running any actual task. The SSH connection works. So the problem is one layer deeper.

Why This Happens

Before executing any module, Ansible uploads a small Python script to the remote host's temp directory. If it can't write there, everything stops before a single task runs. The causes fall into a few buckets:

  • /tmp is mounted with the noexec flag โ€” Ansible uploads scripts there but can't execute them
  • The remote user lacks write permission on the temp directory
  • The login shell is broken, or set to /sbin/nologin or /bin/false
  • $HOME or $TMPDIR is not writable or doesn't exist
  • SELinux or AppArmor blocking filesystem access
  • A custom remote_tmp in ansible.cfg pointing to a path that doesn't exist

Step-by-Step Fix

Step 1 โ€” Verify SSH and basic write access manually

SSH in as the exact user Ansible uses, then probe the temp directory:

ssh deploy@web01

# Can the user write to /tmp?
touch /tmp/ansible_test && echo "OK" && rm /tmp/ansible_test

# Check /tmp mount flags
mount | grep /tmp

Spot noexec in the mount output? That's your culprit โ€” go straight to Step 2.

Step 2 โ€” Fix noexec on /tmp

When /tmp is mounted noexec, redirect Ansible's temp directory somewhere executable. Add this to ansible.cfg:

[defaults]
remote_tmp = ~/.ansible/tmp

Or override it per-play if you don't want to touch the global config:

- hosts: webservers
  vars:
    ansible_remote_tmp: /var/tmp/.ansible
  tasks:
    - name: your task here
      ...

/var/tmp is usually not mounted noexec, making it a reliable fallback. Confirm it's writable before relying on it.

Step 3 โ€” Check the remote user's login shell

An invalid shell means the environment never gets configured โ€” Ansible can authenticate but can't do anything useful after that:

# On the remote host
grep deploy /etc/passwd
# Good: deploy:x:1001:1001::/home/deploy:/bin/bash
# Bad:  deploy:x:1001:1001::/home/deploy:/sbin/nologin

Fix a broken shell with:

sudo usermod -s /bin/bash deploy

Step 4 โ€” Confirm $HOME exists and is writable

ssh deploy@web01
ls -la ~
echo $HOME

Missing home directory? Create it:

sudo mkhomedir_helper deploy
# or do it manually
sudo mkdir -p /home/deploy && sudo chown deploy:deploy /home/deploy

Step 5 โ€” Check the TMPDIR environment variable

A stale TMPDIR pointing to a non-existent path will trip Ansible up even when /tmp itself is fine:

ssh deploy@web01 'echo $TMPDIR'

If it points somewhere that doesn't exist, unset it or create the path. While you're here, consider enabling pipelining:

[ssh_connection]
pipelining = True

Pipelining pipes module code over stdin instead of writing a temp file first. It sidesteps this class of error entirely โ€” and cuts SSH round-trips, so your playbooks run faster as a bonus.

Step 6 โ€” Check SELinux or AppArmor

On RHEL/CentOS with SELinux enforcing, look for AVC denials targeting the temp path:

sudo ausearch -m avc -ts recent | grep ansible
# or
sudo journalctl -xe | grep denied

Quick sanity check โ€” temporarily switch to permissive mode (don't leave it this way):

sudo setenforce 0
# Re-run the playbook to confirm SELinux was blocking it
sudo setenforce 1

If that clears the error, write a proper SELinux policy rule. Disabling enforcement is a test, not a fix.

Step 7 โ€” Run with max verbosity

Still stuck? Crank verbosity up to 4 and filter for temp paths to see exactly what Ansible is attempting:

ansible-playbook site.yml -i inventory -vvvv 2>&1 | grep -i tmp

The output will show the exact directory Ansible tried to write to โ€” which usually makes the root cause obvious.

Verify the Fix

# Ping the host โ€” this exercises the full temp directory setup path
ansible web01 -m ping -i inventory

# Expected output:
web01 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

SUCCESS here means Ansible created its temp files and executed a module cleanly. You're good.

Tips

For CI/CD pipelines where you can't control /tmp mount flags, pin remote_tmp to a subdirectory under $HOME. It's always writable, always executable, and doesn't depend on how ops team mounted things:

[defaults]
remote_tmp = ~/.ansible/tmp
local_tmp  = ~/.ansible/tmp

Pair that with pipelining enabled and you've eliminated the entire temp-file problem class for most setups:

[ssh_connection]
pipelining = True

One gotcha: pipelining requires requiretty to be off in /etc/sudoers when using become. Add Defaults !requiretty to sudoers, otherwise sudo will refuse to run without a TTY and you'll trade one error for another.

If you're setting permissions on remote_tmp by hand and can't remember the right octal values, the Unix Permissions Calculator on ToolCraft saves you from doing the mental arithmetic.

Related Error Notes