Fix Redis Connection Refused in Django
Fix Redis connection refused errors from Django caches, sessions, Celery, and Channels: service status, bind addresses, Docker hostnames, TLS URLs, auth, and limits.
Problem statement
Django pages that use the cache, sessions stored in Redis, Celery task submission, or Channels start failing with:
redis.exceptions.ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused.
Celery workers log:
[ERROR/MainProcess] consumer: Cannot connect to redis://127.0.0.1:6379/0: Error 111 connecting to 127.0.0.1:6379. Connection refused..
Trying again in 2.00 seconds...
"Connection refused" is a precise network error: the client reached the host, but nothing accepted connections on that port. It is different from:
- Timeout (
Error 110orTimeout connecting to server): packets are dropped, usually by a firewall or security group, or the host is unreachable - Name resolution errors (
Error -2 connecting to redis:6379. Name or service not known): the hostname does not resolve, common in Docker when services are on different networks NOAUTHorWRONGPASS: connected, but authentication failedmax number of clients reached: connected, but Redis refused more clients
Quick answer
- Check Redis is running on the target host:
systemctl status redis-serverordocker compose ps redis. - Check which address and port Redis listens on:
sudo ss -ltnp | grep 6379. - Match Django's URL to that address. In Docker Compose, use the service name (
redis://redis:6379/0), never127.0.0.1. - For remote Redis, confirm
bindincludes the private interface and the firewall allows the app servers. - For managed Redis with TLS, use
rediss://and the provider's port. - Restart the Django and Celery processes after changing URLs.
- Verify with
redis-cli -u "$REDIS_URL" pingfrom the same host or container as the app.
Step-by-step solution
1. Is Redis running?
Local service:
systemctl status redis-server --no-pager
sudo journalctl -u redis-server -n 50 --no-pager
Redis fails to start for reasons you will see in the journal: an invalid directive after editing redis.conf, a port already in use, a dir that is not writable, or a failed AOF or RDB load after a crash or full disk.
Docker Compose:
docker compose ps redis
docker compose logs --tail=50 redis
If the container restarts repeatedly, the logs show why.
2. What is Redis listening on?
sudo ss -ltnp | grep -E ':6379\b'
Examples:
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=812,fd=6))
LISTEN 0 511 [::1]:6379 [::]:* users:(("redis-server",pid=812,fd=7))
This Redis accepts only local connections. A Django process on another server gets "connection refused" (or a timeout, depending on the firewall) even though Redis is healthy.
If nothing is listening, Redis is not running or uses another port or a Unix socket; check port and unixsocket in /etc/redis/redis.conf.
3. Match the client URL
Print what the app uses, in the environment of the failing process:
python manage.py shell -c "
from django.conf import settings
print(settings.CACHES['default'].get('LOCATION'))
print(getattr(settings, 'CELERY_BROKER_URL', None))
"
Test the exact URL from the same machine or container:
redis-cli -u "redis://127.0.0.1:6379/0" ping
With a password, URLs have the form redis://:password@host:6379/0 (or redis://username:password@host:6379/0 with ACL users). Avoid putting passwords on the command line on shared machines; REDISCLI_AUTH is an alternative.
4. Docker: 127.0.0.1 inside a container is the container itself
In Docker Compose, each container has its own network namespace. 127.0.0.1 in the web container refers to the web container, where no Redis runs. Use the Compose service name:
services:
web:
environment:
REDIS_URL: redis://redis:6379/0
depends_on:
- redis
worker:
environment:
CELERY_BROKER_URL: redis://redis:6379/1
redis:
image: redis:7
Test from inside the app container:
docker compose exec web python -c "import redis, os; print(redis.Redis.from_url(os.environ['REDIS_URL']).ping())"
depends_on only controls start order, not readiness. Redis starts quickly, but add a health check and condition: service_healthy if the app connects immediately on boot.
If Redis runs on the host and the app in Docker, 127.0.0.1 still does not work from the container. Either run Redis in Compose, or connect to the host's address on the Docker bridge, with Redis bound to that interface and firewalled from the outside.
5. Remote Redis on another server
On the Redis server, in /etc/redis/redis.conf:
bind 127.0.0.1 10.0.1.20
protected-mode yes
requirepass use-a-long-generated-password
10.0.1.20 is the private interface address. Protected mode rejects connections from non-loopback addresses when no password is set, which is why remote connections can fail after only changing bind. Setting a password (or ACL users) satisfies protected mode.
Allow the port only from app servers:
sudo ufw allow from 10.0.1.10 to any port 6379 proto tcp
sudo ufw allow from 10.0.1.11 to any port 6379 proto tcp
Restart Redis and test from an app server:
redis-cli -h 10.0.1.20 -a 'use-a-long-generated-password' --no-auth-warning ping
Never bind Redis to a public interface without network restrictions.
6. Managed Redis and TLS
Managed services commonly require TLS. A redis:// URL to a TLS-only endpoint fails, often with connection resets or timeouts rather than a clear message. Use rediss://:
REDIS_URL=rediss://default:password@my-redis.example-provider.com:6380/0
Celery with TLS Redis may also need SSL options for the broker and backend, such as certificate requirements:
import ssl
CELERY_BROKER_USE_SSL = {"ssl_cert_reqs": ssl.CERT_REQUIRED}
CELERY_REDIS_BACKEND_USE_SSL = {"ssl_cert_reqs": ssl.CERT_REQUIRED}
Check your provider's documentation for the port and certificate authority.
7. Connection limits and resource problems
If errors are intermittent and Redis is running:
redis-cli INFO clients | grep -E "connected_clients|blocked_clients"
redis-cli CONFIG GET maxclients
Hitting maxclients produces a different error (max number of clients reached), but a Redis instance that is restarting because of memory pressure produces brief connection refused errors. Check for OOM kills of redis-server in the kernel log.
8. Make Django resilient to Redis outages
Decide what should happen when Redis is unavailable:
- Cache: wrap non-critical cache access so a failure falls back to computing the value.
- Sessions:
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"keeps sessions in the database, so a cache outage does not log users out. - Celery submission: a broker outage makes
.delay()raise after retries; record critical work in the database first and enqueue from a retry loop or outbox pattern.
Set short socket timeouts so requests fail fast instead of hanging:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.environ["REDIS_URL"],
"OPTIONS": {"socket_connect_timeout": 2, "socket_timeout": 2},
}
}
9. Verify
redis-cli -u "$REDIS_URL" ping
python manage.py shell -c "from django.core.cache import cache; cache.set('redis-check', 1, 10); print(cache.get('redis-check'))"
celery -A config inspect ping
Verification check:
PONGfrom the app host or container- the Django cache round-trip prints
1 - Celery workers reply to
inspect ping - Redis is not reachable from outside the private network
Explanation
"Connection refused" means the TCP handshake reached a host that actively rejected it, because no process listens on that address and port. With Redis, that is almost always one of three situations: Redis is down, Redis listens only on loopback while the client connects over the network, or the client targets the wrong host because of container networking. Checking the listening socket on the server and testing the exact URL from the client's own environment separates those cases in a couple of commands.
Edge cases / notes
IPv6 localhost
A URL with localhost may resolve to ::1 first. If Redis listens only on 127.0.0.1, IPv6 attempts are refused. Use 127.0.0.1 explicitly or bind both.
Unix sockets
For Redis on the same host, a Unix socket avoids TCP entirely: unix:///run/redis/redis-server.sock?db=0. The app user must have permission on the socket, typically through the redis group.
Sentinel and cluster
Redis Sentinel and Redis Cluster need client support and different URLs or options. A plain redis:// URL pointing at a Sentinel port results in protocol errors, not normal operation.
A connectivity checklist script
When an incident starts, running the same checks every time saves minutes. This script tests DNS, TCP, and the Redis protocol for a URL, from the machine or container where it runs:
# scripts/check_redis.py
import os
import socket
import sys
from urllib.parse import urlparse
import redis
url = os.environ.get("REDIS_URL") or sys.argv[1]
parsed = urlparse(url)
host, port = parsed.hostname, parsed.port or 6379
try:
addresses = {info[4][0] for info in socket.getaddrinfo(host, port)}
print(f"DNS ok: {host} -> {', '.join(sorted(addresses))}")
except socket.gaierror as exc:
sys.exit(f"DNS failed for {host}: {exc}")
try:
socket.create_connection((host, port), timeout=3).close()
print(f"TCP ok: {host}:{port}")
except OSError as exc:
sys.exit(f"TCP failed for {host}:{port}: {exc}")
try:
client = redis.Redis.from_url(url, socket_connect_timeout=3, socket_timeout=3)
print("PING:", client.ping())
print("server version:", client.info("server").get("redis_version"))
except redis.RedisError as exc:
sys.exit(f"Redis protocol or auth failed: {exc}")
Run it inside the web container, the worker container, and on the host. Different results between them point directly at a networking or configuration difference. The script prints no credentials.
Health checks for Redis in Compose
redis:
image: redis:7
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" --no-auth-warning ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 10
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
Combine it with depends_on: redis: condition: service_healthy on services that connect at startup, such as Celery workers.
Common mistakes
- Using
127.0.0.1for Redis inside Docker containers. - Changing
bindto0.0.0.0on a public server without a firewall. - Updating the web environment but not the Celery worker's.
- Using
redis://for a TLS-only managed endpoint.
Next steps
- Configure Redis correctly with Set Up Redis Caching for Django in Production.
- Review broker settings in Configure Redis and Celery for Django Production.
- If tasks are sent but never finish, see Fix Celery Tasks Stuck in PENDING.
- The same container networking rules apply to databases: Fix Django Cannot Connect to Postgres in Docker Compose.
FAQ
What does "Error 111 connecting to 127.0.0.1:6379. Connection refused" mean?
Nothing is listening on port 6379 at that address from the client's point of view. Redis may be stopped, bound to another interface, or running in a different container or host.
Why can't my Django container connect to Redis on localhost?
Inside a container, localhost is the container itself. Use the Docker Compose service name, such as redis, as the hostname.
How do I allow remote connections to Redis for Django?
Bind Redis to the private network interface, set a password or ACL user, and allow port 6379 only from your application servers in the firewall.
Do I need rediss:// for managed Redis?
Use rediss:// whenever the provider requires TLS, which most managed services do. Plain redis:// connections to TLS endpoints fail.