LEARN · DEBUGGING GUIDE

Debugging redis-py Connection Refused Errors

Your Python app says 'Connection refused' when talking to Redis? I'll show you exactly how to find the root cause—from network issues to Redis config to client code mistakes.

IntermediatePython7 min read

What this usually means

The 'Connection refused' error means the TCP SYN packet sent by redis-py reached the target host but no process is listening on the specified port (default 6379). This is fundamentally different from a timeout (no reply) or network unreachable (no route). The cause is almost always one of: Redis server is not running, Redis is bound to a different interface (e.g., only 127.0.0.1 but client connects to a public IP), a firewall is dropping the connection, or the client is connecting to the wrong host/port. In containerized environments, common causes include port mapping mismatches or services starting in the wrong order.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `redis-cli ping` from the same host as your Python app. If it returns PONG, the server is up and listening. If it fails, the problem is on the server side.
  • 2Check if Redis is running: `systemctl status redis-server` or `ps aux | grep redis`. If not, start it: `sudo systemctl start redis-server`.
  • 3Verify the listening address and port: `sudo netstat -tlnp | grep 6379`. Look for the local address (0.0.0.0:6379 for all interfaces, 127.0.0.1:6379 for localhost only).
  • 4Test connectivity from your app's host using `nc -vz <redis-host> 6379`. If connection refused, the problem is between the app and Redis.
  • 5Check firewall rules: `sudo ufw status` or `sudo iptables -L -n | grep 6379`. Ensure traffic is allowed from the app's IP.
  • 6If using Docker, verify port mapping: `docker ps --format '{{.Names}} {{.Ports}}'` and ensure -p 6379:6379 is correct.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search/etc/redis/redis.conf — check bind directive and protected-mode
  • search/var/log/redis/redis-server.log — Redis startup logs, may show errors
  • searchApplication environment variables: REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
  • searchDockerfile or docker-compose.yml for port mappings and network config
  • searchCloud security groups (e.g., AWS EC2 security group inbound rules for port 6379)
  • searchKubernetes Service and Pod YAML for targetPort and containerPort
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningRedis server not running or crashed due to OOM or misconfiguration.
  • warningRedis bind directive set to 127.0.0.1 only, but client connects from another host.
  • warningProtected-mode enabled (default) and client connects without password from non-loopback interface.
  • warningFirewall (iptables, ufw, cloud security group) blocking inbound TCP 6379.
  • warningDocker container port not published or mapped to different host port.
  • warningTypo in hostname or port in redis-py connection string (e.g., 6378 instead of 6379).
  • warningRedis listening on IPv6 but client resolves hostname to IPv4 address.
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildStart Redis: `sudo systemctl start redis-server` and enable: `sudo systemctl enable redis-server`.
  • buildUpdate bind directive in redis.conf to 0.0.0.0 or specific interface IP, then restart Redis.
  • buildDisable protected-mode or set a password (`requirepass`) in redis.conf and add password to client connection.
  • buildAdd firewall rule: `sudo ufw allow from <app-ip> to any port 6379`.
  • buildIn Docker, ensure port mapping: `ports: - '6379:6379'` in docker-compose.yml.
  • buildFix connection string: use correct host, port, and password. For example: `redis.Redis(host='redis.example.com', port=6379, password='mypass')`.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun `redis-cli -h <host> -p <port> ping` from the application host and get PONG.
  • verifiedExecute a simple Python script: `r = redis.Redis(host='...', port=...); print(r.ping())` should print True.
  • verifiedCheck Redis server log for any errors after restart.
  • verifiedMonitor netstat to confirm Redis is listening on the expected interface.
  • verifiedRun a connectivity test from the app container: `docker exec <container> redis-cli -h <redis-host> ping`.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningOnly checking localhost with redis-cli when the app connects to a different IP.
  • warningNot restarting Redis after modifying redis.conf.
  • warningSetting bind to 0.0.0.0 without disabling protected-mode or setting a password, exposing Redis to the internet.
  • warningAssuming Docker containers on the same host can communicate via localhost; they need to use the service name or host IP.
  • warningHardcoding connection parameters; use environment variables instead.
  • warningIgnoring SELinux or AppArmor policies that may block Redis.
( 07 )War story

Production Outage: Redis Connection Refused After Deployment

Senior Backend EngineerPython 3.10, redis-py 4.3, Docker, AWS ECS, Redis 6.2 (ElastiCache)

Timeline

  1. 09:15Deploy new version of user-service to ECS.
  2. 09:17PagerDuty alert: high error rate in user-service. Error: 'Connection refused'.
  3. 09:20Check ECS task logs: repeated redis.exceptions.ConnectionError: Error 111 connecting to redis-cluster.xyz.amazonaws.com:6379.
  4. 09:22Verify Redis ElastiCache cluster status: available, no alarms.
  5. 09:25Run redis-cli from a jump box: works. Suspicious.
  6. 09:30Check ECS security group: inbound rule for port 6379 missing for new task's subnet.
  7. 09:32Update security group to allow inbound from app subnet.
  8. 09:35Tasks restart automatically; error rate drops to zero.

We had just rolled out a new microservice for user profile caching. The deployment went through without a hitch—no build errors, no config warnings. But within two minutes, our error rate spiked from 0.1% to 45%. The logs were screaming 'Connection refused' on every Redis call. My first instinct was that the ElastiCache cluster had gone down. But the AWS console showed it was healthy, and my colleague could ping it from a bastion host.

I ssh'd into an ECS container and tried `redis-cli -h redis-cluster.xyz.amazonaws.com ping`. It hung for a few seconds then returned 'Connection refused'. That was the crucial sign: the container could reach the host but nothing was listening on port 6379. But the ElastiCache was definitely listening. I checked the security group attached to the ElastiCache—it had an inbound rule allowing port 6379 from the old subnet, but our new service was deployed to a different subnet. The rule was too specific.

I quickly added a new inbound rule allowing TCP 6379 from the new subnet's CIDR. Within seconds, the tasks reconnected and errors dropped to zero. Post-mortem: we had updated the VPC configuration but forgot to update the security group. The fix was a one-line rule addition, but the lesson was to always verify network path changes with a simple `nc -vz` from the actual runtime environment, not from a jump box.

Root cause

ElastiCache security group inbound rule did not include the new ECS task subnet, blocking TCP 6379.

The fix

Added inbound rule: TCP 6379 from 10.0.2.0/24 (new subnet).

The lesson

Always verify connectivity from the exact runtime environment; security group rules are often the forgotten link.

( 08 )Understanding the TCP Connection Refused Error

When redis-py raises ConnectionError with 'Connection refused', it's a TCP-level error. The client sends a SYN packet to the server IP:port, and the server responds with a RST (reset) because no process is listening on that port. This is distinct from a timeout (no response) or network unreachable (no route).

To confirm this, use `strace -e trace=network python -c "import redis; r = redis.Redis(); r.ping()"`. You'll see the connect syscall fail with ECONNREFUSED (error 111 on Linux, 61 on macOS). This immediately tells you the server is reachable but not listening on that port.

( 09 )Redis Configuration Pitfalls: bind and protected-mode

The default redis.conf binds to 127.0.0.1 and enables protected-mode. This means Redis only listens on localhost, and if you connect from any other interface (e.g., Docker host, remote server), you'll get connection refused. The fix is either to change bind to 0.0.0.0 or the specific network interface, and either disable protected-mode or set a password.

Protected-mode is a safety net: if Redis is bound to non-localhost interfaces and no password is set, it will reject all connections except from localhost. This is to prevent accidental exposure. To disable, set `protected-mode no` in redis.conf, but better to set `requirepass` and use authentication in your client.

( 10 )Container and Orchestration Specifics

In Docker, even if Redis runs in a container and you expose port 6379, your app container cannot connect via localhost unless you use `--network host`. Instead, use the service name in docker-compose or the container IP. In Kubernetes, ensure the Service's targetPort matches the container's port, and that network policies allow traffic.

Always test with a simple `redis-cli -h <service-name> ping` from within the app container. If that fails, check port mapping and network policies.

( 11 )Cloud Managed Redis (ElastiCache, Memorystore)

Managed Redis services often have security groups or firewall rules separate from the Redis config. In AWS ElastiCache, the security group must allow inbound TCP 6379 from the client's security group. If you move your app to a new subnet or VPC, you must update the rule. Similarly, GCP Memorystore uses authorized networks.

A common mistake is to test from a jump box that is in a different network (e.g., a bastion host in the same VPC but with a different security group). The jump box might work, but the app container might not. Test from the actual runtime environment.

( 12 )Advanced: IPv6 vs IPv4 and DNS Resolution

If your Redis hostname resolves to an IPv6 address (AAAA record) but the client only supports IPv4, or vice versa, you may get connection refused. Python's socket library by default tries IPv4 first, then IPv6. But if Redis is only listening on IPv4 and the DNS returns an IPv6 address, the client will fail. Force IPv4 by using an IP address or setting the socket type.

Check with `getent ahosts <redis-host>` to see all IPs. If needed, set `redis.Redis(host='<ipv4>')`. In docker-compose, you can set `sysctls: net.ipv6.conf.all.disable_ipv6=1` to avoid issues.

Frequently asked questions

Why does redis-cli work but redis-py does not?

redis-cli might be connecting to localhost while your code connects to a different host. Or, redis-cli might use a different network namespace (e.g., inside a container). Also, check that your Python code uses the same host and port. If everything matches, the issue could be protected-mode: redis-cli might be connecting from localhost, but your app from another host.

How do I check if Redis is listening on all interfaces?

Run `sudo netstat -tlnp | grep 6379`. If you see 127.0.0.1:6379, it's only on localhost. If you see 0.0.0.0:6379, it's on all interfaces. Alternatively, `ss -tlnp | grep 6379`.

What does 'Error 111' mean and how is it different from 'Error 61'?

Both are the same error—connection refused—but with different errno values on different systems. On Linux, it's ECONNREFUSED with value 111. On macOS/FreeBSD, it's ECONNREFUSED with value 61. The error string is the same: 'Connection refused'.

Can a full Redis connection pool cause connection refused?

No, a full pool causes a timeout, not a connection refused. Connection refused is a TCP-level rejection. Pool exhaustion leads to 'TimeoutError' or 'Connection pool exhausted'.