TL;DR
Your Node.js process sent a TCP connection request and got... nothing back. No response, just silence until the timeout kicked in. That's ETIMEDOUT. The usual suspects: a firewall swallowing your packets, a wrong IP, the database isn't actually running, or the server is too slammed to respond. Run telnet or nc first to confirm it's a network issue, then hunt down the specific blocker.
What This Error Means
The full stack trace looks like this:
Error: connect ETIMEDOUT 203.0.113.42:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) {
errno: -110,
code: 'ETIMEDOUT',
syscall: 'connect',
address: '203.0.113.42',
port: 5432
}
Node.js sent a TCP SYN packet to port 5432. No SYN-ACK came back. After the OS timeout window (typically 20โ75 seconds), it gave up.
This is different from ECONNREFUSED, where the remote host actively rejects your connection. With ETIMEDOUT, packets just vanish โ into a firewall, a dead route, or a service too overwhelmed to respond. That distinction matters when debugging: ECONNREFUSED means the host is reachable, ETIMEDOUT means something in between is blocking you.
Root Causes
- Firewall blocking the port โ the #1 cause on AWS, GCP, and any hardened Linux server. Security Groups, UFW, and iptables all silently drop packets by default.
- Wrong IP address or hostname โ the service is alive, you're just pointing at the wrong address. A typo in
.envcan waste an hour. - Service not listening on that port โ database crashed, or it bound to
127.0.0.1instead of0.0.0.0. - Network routing issue โ VPN disconnected, VPC peering not configured, or simply no route to the host.
- Connection pool exhausted โ all 10 connections are busy, new requests queue up, then die waiting for a slot.
Step 1 โ Test Connectivity Before Touching Code
Don't start tweaking timeouts or rewriting connection logic yet. Confirm first whether this is a network problem or an application problem:
# Test TCP connectivity directly
telnet 203.0.113.42 5432
# No telnet? Use nc instead
nc -zv 203.0.113.42 5432 -w 5
# Verify DNS resolves to the right IP
nslookup your-db-host.example.com
dig your-db-host.example.com
Three outcomes to watch for:
- Hangs indefinitely โ firewall is dropping packets. Go to Step 2.
- Connection refused โ host is reachable but nothing is listening. Go to Step 3.
- Connects immediately โ network is fine. The bug is in your Node.js config. Go to Step 4.
Step 2 โ Unblock the Firewall
Cloud firewalls silently drop packets โ that's exactly what ETIMEDOUT looks like from the outside.
AWS EC2 / RDS
# Inspect security group inbound rules
aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
# For RDS: Console โ RDS โ Your DB โ Connectivity & security โ VPC security groups
The inbound rule must allow TCP on your target port from your app server's IP or security group. A common mistake: allowing 0.0.0.0/0 in dev but forgetting to add the app server's security group ID in production.
Linux server (UFW)
# See current rules
sudo ufw status verbose
# Open a port globally
sudo ufw allow 5432/tcp
# Or restrict to a specific IP (safer in production)
sudo ufw allow from 10.0.1.50 to any port 5432
iptables
sudo iptables -L INPUT -n -v | grep 5432
Step 3 โ Verify the Service Is Actually Listening
SSH into the database server and check what's bound to the port:
# See which process is listening, and on which interface
ss -tlnp | grep 5432
# or
netstat -tlnp | grep 5432
# Check service health
systemctl status postgresql
systemctl status mysql
systemctl status mongod
The bound address tells the whole story. 127.0.0.1:5432 means the service only accepts local connections. 0.0.0.0:5432 means it's open to all interfaces (firewall rules still apply).
Seeing 127.0.0.1? Fix it in the service config. For PostgreSQL, open /etc/postgresql/*/main/postgresql.conf and update this line:
listen_addresses = 'localhost' # change this
listen_addresses = '*' # to this, then restart postgresql
Step 4 โ Tune Timeout Settings in Node.js
Network checks out fine but timeouts still happen under load? Connection pool exhaustion is the likely culprit. New requests queue up waiting for a free slot, wait too long, and die with ETIMEDOUT.
With pg (PostgreSQL)
const { Pool } = require('pg');
const pool = new Pool({
host: 'your-db-host',
port: 5432,
database: 'mydb',
user: 'user',
password: 'password',
connectionTimeoutMillis: 10000, // wait up to 10s for a free connection
max: 20, // up from default 10 if your DB can handle it
idleTimeoutMillis: 30000,
});
With mysql2
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'your-db-host',
port: 3306,
user: 'user',
password: 'password',
database: 'mydb',
connectTimeout: 10000,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
With axios (HTTP requests)
const axios = require('axios');
const client = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000, // 10 seconds
});
try {
const response = await client.get('/endpoint');
} catch (err) {
if (err.code === 'ETIMEDOUT' || err.code === 'ECONNABORTED') {
console.error('Request timed out โ check network or bump the timeout value');
}
throw err;
}
Step 5 โ Add Retry Logic for Transient Failures
Not every ETIMEDOUT means something is broken. Brief network hiccups, rolling restarts, and cold-start delays can all trigger a one-off timeout that resolves on its own. A simple retry with exponential backoff handles these without alerting anyone:
async function connectWithRetry(connectFn, retries = 3, delay = 2000) {
for (let i = 0; i setTimeout(res, delay * (i + 1)));
} else {
throw err;
}
}
}
}
// Usage
await connectWithRetry(() => pool.query('SELECT 1'));
This retries up to 3 times with delays of 2s, 4s, and 6s. Adjust based on how quickly your service typically recovers.
Verify the Fix
Once you've made a change, confirm it actually worked before redeploying:
# Quick TCP test from the app server
nc -zv your-db-host 5432 -w 5
# Expected: Connection to your-db-host 5432 port [tcp/postgresql] succeeded!
# Or test directly with Node.js
node -e "
const net = require('net');
const socket = net.createConnection({ host: 'your-db-host', port: 5432, timeout: 5000 });
socket.on('connect', () => { console.log('Connected!'); socket.destroy(); });
socket.on('timeout', () => { console.log('Timed out'); socket.destroy(); });
socket.on('error', (err) => console.log('Error:', err.message));
"
Tips
When debugging firewall rules, you often need to check whether your app server's IP falls within an allowed CIDR block. The Subnet Calculator on ToolCraft handles this quickly โ paste in your CIDR and IP, and it tells you immediately if there's a match. Runs entirely in the browser, no data sent anywhere. Handy when you're staring at an AWS Security Group rule like 10.0.0.0/16 and wondering if 10.0.1.50 is actually in range.
Quick Reference
ETIMEDOUTโ packets sent, no response (firewall or dead route)ECONNREFUSEDโ host reached, port rejected the connection (service down)ENOTFOUNDโ DNS resolution failed (wrong hostname)ECONNRESETโ connection established, then abruptly closed by remote

