Fix Redis SSL CERTIFICATE_VERIFY_FAILED With rediss:// in Django and Celery
Fix SSL CERTIFICATE_VERIFY_FAILED errors when Django, Celery, or Channels connect to Redis over rediss:// on Heroku, Upstash, ElastiCache, or Azure: configure ssl_cert_reqs, CA bundles, and broker_use_ssl without disabling verification blindly.
Problem statement
After switching Redis to TLS, or moving to a provider that requires it, connections fail:
redis.exceptions.ConnectionError: Error 1 connecting to example.redis.host:6380.
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate
in certificate chain
Celery may refuse to start with a different message:
ValueError: A rediss:// URL must have parameter ssl_cert_reqs and this must be set
to CERT_REQUIRED, CERT_OPTIONAL, or CERT_NONE
The client is verifying the server certificate, as it should, and cannot build a chain to a trusted certificate authority. Some providers, notably Heroku Redis, use self-signed certificates. Others use public CAs but your container lacks CA certificates, or the host name you connect to does not match the certificate.
- Find out how your provider signs certificates: public CA (Upstash, ElastiCache, Azure) or self-signed (Heroku Redis).
- Public CA: install
ca-certificatesin the image and connect using the provider's host name. - Self-signed with a provided CA file: pass it with
ssl_ca_certs. - Heroku Redis: set
ssl_cert_reqs=noneexplicitly for every client, since Heroku documents this for its self-signed certificates. - For Celery, add
?ssl_cert_reqs=CERT_REQUIRED(orCERT_NONE) torediss://URLs, or setbroker_use_ssl.
Step-by-step solution
1. Inspect the certificate
openssl s_client -connect example.redis.host:6380 -servername example.redis.host </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -subject -dates
An issuer such as Amazon or Let's Encrypt means a public CA. An issuer equal to the subject, or a private CA name, means you need the provider's CA file or must disable verification.
2. Public CA: fix the trust store
Slim images may lack CA certificates:
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
Also connect with the host name on the certificate, not an IP address or an internal alias, or verification fails on the name check even with a valid chain.
3. Configure Django's cache
With django-redis:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.environ["REDIS_URL"], # rediss://...
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": "required"},
},
}
}
With a private CA, add "ssl_ca_certs": "/etc/ssl/certs/provider-ca.pem". For Heroku Redis, use "ssl_cert_reqs": None as Heroku's documentation describes. Django's built-in Redis backend accepts the same keys under "OPTIONS".
4. Configure Celery
Celery requires an explicit choice for rediss:// URLs. Either in the URL:
CELERY_BROKER_URL = os.environ["REDIS_URL"] + "?ssl_cert_reqs=CERT_REQUIRED"
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
or in settings:
import ssl
CELERY_BROKER_USE_SSL = {"ssl_cert_reqs": ssl.CERT_REQUIRED}
CELERY_REDIS_BACKEND_USE_SSL = {"ssl_cert_reqs": ssl.CERT_REQUIRED}
Use CERT_NONE only for providers like Heroku Redis that require it. If your REDIS_URL already contains a query string, append with & instead of ?.
5. Configure Channels
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [{"address": os.environ["REDIS_URL"], "ssl_cert_reqs": None}]},
}
}
Drop ssl_cert_reqs when the provider uses a public CA.
Explanation
redis-py wraps the socket in TLS when the URL scheme is rediss:// and, since redis-py 4, verifies the certificate by default using the system CA store. Verification fails when the chain ends in a certificate the store does not trust or when the host name does not match. Celery adds its own check because a rediss:// URL without an explicit choice was historically ambiguous. Disabling verification still encrypts traffic but no longer proves you are talking to your Redis; it is acceptable only where the provider gives no CA to verify against.
Edge cases / notes
- Heroku certificate changes. Heroku Redis has changed TLS behavior over time; check their current guidance for the plan you use.
- Corporate proxies. TLS-inspecting proxies replace certificates; add the proxy CA to the trust store rather than disabling verification.
- Old redis-py. Versions before 4 did not verify by default, so an upgrade can turn a silent setup into this error.
Next steps
- If authentication fails after TLS works, see Fix Redis NOAUTH Authentication Required.
- For outbound HTTPS errors from Django, read Fix Django SSL CERTIFICATE_VERIFY_FAILED for Outbound Requests.
- Using a serverless Redis? See Use Upstash Redis With Django.
FAQ
Is ssl_cert_reqs=none insecure?
It keeps encryption but skips identity verification, so a network attacker could impersonate the server. Use it only when the provider requires it.
Why does Celery demand ssl_cert_reqs in the URL?
Celery requires an explicit verification choice for rediss:// URLs so the security behavior is never accidental.
Do I need TLS on a private network?
Many managed providers require it. Where optional, it still protects credentials and data from other hosts on the network.
Why does it work locally but fail in Docker?
The slim image probably lacks ca-certificates, so even public CAs cannot be verified.