The Situation
Your playbook is downloading a file or polling an internal API via get_url or uri. Everything looks fine — until this lands in your terminal:
FAILED! => {
"msg": "Status code was -1 and not [200]: Request failed: "
}
Status code -1 means the connection never got through. Common culprits:
- Internal server running a self-signed certificate
- Corporate SSL inspection proxy (Zscaler, Forcepoint, Squid with SSL bump)
- Staging or dev environment with a cert signed by an internal CA Python doesn't know about
- An expired server cert nobody caught — until Ansible did
Why Python/Ansible Rejects the Cert
Ansible's uri and get_url modules use Python's urllib internally. It validates SSL certificates against the system CA bundle — or the certifi package bundle if that's installed. When the server's cert isn't signed by a CA in that bundle, Python kills the connection and returns status -1. No prompt, no warning. Just a hard stop.
Browsers handle this differently — they ship their own trust store and show a click-through warning. Python doesn't offer that option.
Quick Fix — Disable Verification (Use With Caution)
Need it working right now on a controlled internal network? Add validate_certs: false:
- name: Download file from internal server
get_url:
url: "https://internal.example.com/files/package.tar.gz"
dest: /tmp/package.tar.gz
validate_certs: false
- name: Hit internal API
uri:
url: "https://internal-api.example.com/health"
method: GET
validate_certs: false
register: api_response
This disables SSL verification entirely. Acceptable for internal tooling behind a firewall. Not acceptable for anything touching the public internet or sensitive data — you're wide open to MITM attacks.
Permanent Fix — Add Your CA Certificate
The correct approach is teaching Python to trust your internal CA. Three options, depending on your setup.
Option 1: Point Ansible to a Specific CA Bundle
The ca_path parameter lets you specify exactly which cert to trust, per task:
- name: Download with custom CA
get_url:
url: "https://internal.example.com/files/package.tar.gz"
dest: /tmp/package.tar.gz
ca_path: /etc/ssl/certs/internal-ca.crt
- name: URI with custom CA
uri:
url: "https://internal-api.example.com/status"
ca_path: /etc/ssl/certs/internal-ca.crt
Get the CA cert from your infra or security team. For self-signed server certs, pull it directly from the control node:
# Grab the cert from the server
openssl s_client -connect internal.example.com:443 -showcerts </dev/null 2>/dev/null | \
openssl x509 -outform PEM > /etc/ssl/certs/internal-ca.crt
Option 2: Add the CA to the System Trust Store
Add it once, and everything — Ansible, curl, Python scripts — picks it up automatically. Best choice for recurring use.
On Ubuntu/Debian:
sudo cp internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
On RHEL/CentOS/Rocky:
sudo cp internal-ca.crt /etc/pki/ca-trust/source/anchors/internal-ca.crt
sudo update-ca-trust
Once done, drop validate_certs: false or ca_path from your tasks — the system trust store handles it from here.
Option 3: Set the CA Bundle via Environment Variable
No root access? Running in a container? Set REQUESTS_CA_BUNDLE in your playbook's environment block:
- name: Download with CA via env var
get_url:
url: "https://internal.example.com/files/package.tar.gz"
dest: /tmp/package.tar.gz
environment:
REQUESTS_CA_BUNDLE: /path/to/internal-ca.crt
For a permanent setup, export these in the shell profile that launches Ansible (e.g. ~/.bashrc or /etc/profile.d/ansible-ca.sh):
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/internal-ca.crt
export SSL_CERT_FILE=/etc/ssl/certs/internal-ca.crt
Corporate Proxy (SSL Inspection)
Zscaler and similar proxies intercept HTTPS traffic and re-sign every connection with the proxy's own CA. Your Python sees that proxy cert, doesn't trust it, and bails.
Fix: get the proxy's root CA from IT and add it using Option 2 above. Once that CA is in the trust store, all HTTPS through the proxy works — no verification bypass needed.
First, confirm a proxy is actually the culprit:
# Check what cert you're actually getting
openssl s_client -connect internal.example.com:443 </dev/null 2>/dev/null | openssl x509 -text -noout | grep -E "Issuer|Subject"
If Issuer shows your proxy vendor (something like Zscaler Inc) instead of the server's actual CA, that's your problem.
Verify the Fix
Test on the control node before running the full playbook:
# Quick Python check
python3 -c "
import urllib.request
response = urllib.request.urlopen('https://internal.example.com')
print(response.status)
"
# Or inspect the cert chain with curl
curl -v https://internal.example.com 2>&1 | grep -E "SSL|certificate|verify"
Then run a minimal playbook to confirm Ansible is happy:
- name: Verify SSL fix
hosts: localhost
gather_facts: false
tasks:
- name: Check endpoint
uri:
url: "https://internal.example.com/health"
method: GET
register: result
- debug:
msg: "Status: {{ result.status }}"
Status: 200 without validate_certs: false — you're done.
Checklist
- Get the actual CA cert — don't just disable verification
- On Ubuntu:
update-ca-certificates; on RHEL:update-ca-trust - Use
ca_pathfor task-level control, system trust store for a global fix - Behind a corporate proxy? Get the proxy root CA from IT — not the server cert
- Run
openssl s_clientfirst to see exactly what cert chain you're dealing with

