20 min read

Chapter 3 — Error Codes Below Your App

ECONNRESET, Exit Code 137, SQLSTATE 23505 — and What Each One Physically Means

Part 1: Foundations. Prerequisites: Chapters 1–2.


Opening

Your JavaScript runs on top of a stack of other systems: the operating system, the network, TLS, the container or process manager, the database. When one of them fails, it reports the failure with a code — short, cryptic, and extremely precise.

ECONNREFUSED and ETIMEDOUT both mean "could not connect", but they describe completely different physical situations and send you to completely different places. Exit code 137 and exit code 134 both mean "the process died", but one means the container ran out of memory and the other means the JavaScript heap did. SQLSTATE 40001 means "retry this", while 23505 means "never retry this."

This chapter decodes the codes you will see most. The goal is a reflex: see the code, know the layer, know where to look — before opening a single file.


Part 1: Where These Codes Come From

When Node asks the operating system to do something — open a file, connect a socket, listen on a port — it makes a system call (syscall). If the syscall fails, the OS returns an error number called an errno, which has a symbolic name like ECONNREFUSED. Node puts that name on the error as err.code.

Read a Node system error as a sentence:

text
Error: connect ECONNREFUSED 127.0.0.1:5432
         │       │            └── the address and port it tried
         │       └── the code: what went wrong
         └── the syscall: what Node was trying to do

The error object also carries these as fields: err.syscall ('connect'), err.code ('ECONNREFUSED'), err.address and err.port. "Connect refused to port 5432" already tells you it is Postgres and that the problem is before any query ran.

The E prefix means "error". The rest is an abbreviation: CONNREFUSED = connection refused, NOENT = no entry (no such file), ACCES = access denied, ADDRINUSE = address in use.


Part 2: Network Codes — Mapped to the Life of a Connection

A network request goes through stages. Each code belongs to a stage, and knowing the stage tells you where to look.

Stage 1 — DNS lookup fails

CodeWhat physically happenedWhere to look
ENOTFOUND (getaddrinfo ENOTFOUND api.internal)DNS has no record for that nameTypo in the hostname, wrong env var, or an internal name that only resolves inside a particular network (a Docker Compose service name used from your laptop, a VPC-only hostname)
EAI_AGAINDNS lookup failed temporarily — the resolver did not answerThe DNS server is down, overloaded or unreachable; often transient

Stage 2 — TCP connect fails

CodeWhat physically happenedWhere to look
ECONNREFUSEDThe machine answered, but nothing is listening on that port, so it refused immediatelyThe service is not running, wrong port, or it listens only on 127.0.0.1. Inside a container, localhost means the container itself, not your machine or another container
ETIMEDOUTNo answer at all — packets went out and nothing came back before the timeoutA firewall or security group silently dropping traffic, a wrong IP, a host that is down, or a server too overloaded to accept connections
EHOSTUNREACH / ENETUNREACHThere is no network route to that host or networkNetwork config, VPN not connected, wrong subnet

The difference between refused and timed out is the most useful distinction in network debugging. Refused is fast and means "reached the machine, nobody home." Timed out is slow and means "never reached anything that answered." A refused connection points at the service; a timeout points at the network path.

Stage 3 — the connection existed, then broke

CodeWhat physically happenedWhere to look
ECONNRESETThe other side abruptly closed an open connection (sent a TCP reset)The server crashed or restarted mid-request, a proxy or load balancer killed the connection, or a keep-alive race (below)
socket hang up (code ECONNRESET)Node's HTTP client: the server closed the connection without sending any responseServer crash, server-side timeout, or the same keep-alive race
EPIPEYou wrote to a connection the other side had already closedWriting a response after the client disconnected, or streaming to a dead socket

Node's fetch wraps all of these

Node's built-in fetch (powered by a library called undici) throws a generic TypeError: fetch failed and puts the real error in cause:

text
TypeError: fetch failed
  [cause]: Error: connect ECONNREFUSED 127.0.0.1:3000

It also has its own codes, prefixed UND_ERR_: UND_ERR_CONNECT_TIMEOUT (connect took too long), UND_ERR_HEADERS_TIMEOUT (connected, but the server took too long to start responding), UND_ERR_SOCKET (the socket failed). If your error handling only logs err.message, every one of these looks identical: "fetch failed". Always log err.cause.

The keep-alive race — where mysterious ECONNRESETs come from

HTTP clients keep connections open after a request (keep-alive) so the next request can skip the TCP and TLS handshakes. Servers close idle connections after a timeout — Node's HTTP server defaults to 5 seconds. If the client picks an idle connection from its pool at the exact moment the server closes it, the request dies.

#
HTTP client (connection pool)
API server
Connection
1
Request 1 sent on connection C
open
1 / 8

The same race between a load balancer and your app produces intermittent 502 errors, which Chapter 13 investigates in full.


Part 3: Filesystem, Port and Process Codes

CodeTypical messageWhat it means
ENOENTENOENT: no such file or directory, open '/app/config.json'The path does not exist — often a relative path resolved from a different working directory than you assumed
ENOENT (on spawn)spawn git ENOENTThe program you tried to run is not installed or not on PATH in that environment (very common in minimal containers)
EACCESEACCES: permission denied, open '/var/log/app.log'The process's user lacks permission. Also listen EACCES when a non-root process binds a port below 1024
EPERMoperation not permittedThe OS forbids the operation regardless of file permissions; on Windows often a file locked by another process
EADDRINUSElisten EADDRINUSE: address already in use :::3000Another process is already listening on that port — often an old instance of your own server. Find it with lsof -i :3000
EMFILEEMFILE: too many open filesThe process hit its limit of open file descriptors. Usually a leak — sockets or files opened and never closed — and sometimes a genuinely low limit
ENOSPCENOSPC: no space left on deviceThe disk is full. On Linux dev machines, also System limit for number of file watchers reached — the file-watcher limit, not disk
EEXISTEEXIST: file already existsCreating something that is already there
EISDIR / ENOTDIRillegal operation on a directoryYou treated a directory as a file, or the other way round

Node's own ERR_ codes

Node also has codes for its own errors, prefixed ERR_. The ones you will meet most:

CodeMessageWhat it means
ERR_HTTP_HEADERS_SENTCannot set headers after they are sent to the clientYour handler sent a response twice — usually a missing return after res.send() in an error branch, or both a callback and a promise responding
MODULE_NOT_FOUND / ERR_MODULE_NOT_FOUNDCannot find module 'x'The package is not installed in this environment, the path is wrong, or a file extension is missing in ES-module imports
ERR_REQUIRE_ESMrequire() of ES Module … not supportedCommonJS code tried to require an ES-module-only package
ERR_INVALID_ARG_TYPEThe "path" argument must be of type string. Received undefinedYou passed undefined to a Node API — usually a missing env var or config value
ERR_INVALID_URLInvalid URLnew URL() got a relative or malformed string — often a missing base URL env var

Part 4: TLS and Certificate Errors

HTTPS adds a TLS handshake after TCP connect. During it, the client checks the server's certificate: is it signed by a trusted authority, is it still valid, and does it match the hostname? Each failure has its own code.

Node codeBrowser equivalentWhat it means
CERT_HAS_EXPIREDNET::ERR_CERT_DATE_INVALIDThe certificate's validity period has ended (or the client's clock is wrong)
ERR_TLS_CERT_ALTNAME_INVALIDNET::ERR_CERT_COMMON_NAME_INVALIDThe certificate is valid but for a different hostname — you are calling the wrong name, or the cert does not list this one
DEPTH_ZERO_SELF_SIGNED_CERT / SELF_SIGNED_CERT_IN_CHAINNET::ERR_CERT_AUTHORITY_INVALIDSigned by an authority the client does not trust — a self-signed dev cert, or a corporate proxy that intercepts HTTPS with its own certificate
UNABLE_TO_VERIFY_LEAF_SIGNATURE / UNABLE_TO_GET_ISSUER_CERT_LOCALLY(often works in browsers)The server did not send its intermediate certificate. Browsers often fill the gap themselves; Node does not — so "works in the browser, fails from Node"

Never "fix" these with NODE_TLS_REJECT_UNAUTHORIZED=0. It turns off certificate checking for the whole process, which means you will happily talk to an attacker. Fix the certificate, or give Node the right CA with NODE_EXTRA_CA_CERTS.


Part 5: Chrome's net::ERR_ Codes

In the browser, network failures show up in the Console and Network tab as net::ERR_…. They map almost one-to-one onto the Node codes above.

Chrome codeSame asWhat it means
ERR_NAME_NOT_RESOLVEDENOTFOUNDDNS lookup failed
ERR_CONNECTION_REFUSEDECONNREFUSEDNothing listening on that port
ERR_CONNECTION_TIMED_OUTETIMEDOUTNo answer
ERR_CONNECTION_RESETECONNRESETConnection abruptly closed
ERR_EMPTY_RESPONSEsocket hang upServer closed the connection without responding
ERR_INTERNET_DISCONNECTED—The device is offline
ERR_CERT_…TLS codes aboveCertificate problem
ERR_TOO_MANY_REDIRECTS—A redirect loop, often between http and https or between two auth pages
ERR_BLOCKED_BY_CLIENT—A browser extension blocked it — usually an ad blocker matching a URL like /analytics or /track. Not your bug, but your users have it
ERR_ABORTEDAbortErrorThe request was cancelled — navigation, an AbortController, or a superseded request. Often harmless
ERR_FAILED—A generic failure; for cross-origin requests, check the Console for a CORS message
ERR_HTTP2_PROTOCOL_ERROR—The HTTP/2 stream was broken, often by a proxy or a server error mid-response

Part 6: Process Exit Codes and Signals

When a process ends, it reports an exit code: 0 means success, anything else means failure. When a process is killed by a signal, the exit code is 128 + the signal number. This is how you tell "the app crashed" from "something killed the app."

Exit codeMeaningWhat usually happened
0SuccessThe process finished normally
1General failureIn Node: an uncaught exception or unhandled rejection, or process.exit(1)
126Found but not executableMissing execute permission on a script
127Command not foundThe binary is not installed or not on PATH
130Killed by SIGINT (2)Someone pressed Ctrl+C
134Killed by SIGABRT (6)The process aborted itself. In Node, this is how the JavaScript heap running out ends: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
137Killed by SIGKILL (9)Killed forcibly from outside. Most often the container or kernel OOM killer — the process used more memory than its limit. Also kill -9, or an orchestrator that gave up waiting for a graceful stop
139Killed by SIGSEGV (11)Segmentation fault — a crash in native code (a native addon or the runtime itself), not your JavaScript
143Killed by SIGTERM (15)Asked politely to stop. Normal during deploys and scale-downs. The app should catch SIGTERM, finish in-flight requests and exit

134 versus 137 matters. 134 means the V8 heap hit its limit (--max-old-space-size) — a JavaScript memory problem. 137 means the whole process exceeded the container's memory limit — which can include native memory, buffers and the heap together. In Kubernetes, the pod status shows OOMKilled for the second case.

Two more terms you will hear around containers:

  • CrashLoopBackOff (Kubernetes) — the container keeps crashing on startup, so Kubernetes waits longer and longer between restarts. The cause is in the logs of the previous crashed container (kubectl logs --previous).
  • Graceful shutdown — handling SIGTERM by stopping new work, finishing current requests and closing connections. Without it, every deploy cuts off in-flight requests, which shows up as a burst of 502s and ECONNRESETs at deploy time.

Part 7: Database Error Codes — PostgreSQL SQLSTATE

Postgres reports every error with a five-character SQLSTATE code (in Node, err.code). The first two characters are the class, and the class alone tells you a lot.

ClassMeaningRetry?
08Connection exceptionMaybe, with backoff — the connection failed
22Data exception — the value is invalidNo — fix the input
23Integrity constraint violationNo — it is a logic or data problem
25Invalid transaction stateNo — fix the transaction handling
28Authentication failedNo — fix credentials
40Transaction rollbackYes — retry the whole transaction
42Syntax error or access rule violationNo — fix the query or schema
53Insufficient resourcesBack off
57Operator intervention (cancelled, shutting down)Depends

The specific codes you will actually meet:

SQLSTATENameTypical messageWhat it means
23505unique_violationduplicate key value violates unique constraint "users_email_key"A row with that unique value already exists — a duplicate signup, or a retried request that already succeeded
23503foreign_key_violationinsert or update on table "orders" violates foreign key constraintYou referenced a row that does not exist (or deleted one still referenced)
23502not_null_violationnull value in column "user_id" violates not-null constraintA required value was missing — often an undefined from your code
23514check_violationnew row … violates check constraintThe value broke a rule the schema enforces
22P02invalid_text_representationinvalid input syntax for type uuid: "undefined"The value could not be converted to the column type — very often the literal string "undefined" from JavaScript
22001string_data_right_truncationvalue too long for type character varying(50)The string is longer than the column allows
25P02in_failed_sql_transactioncurrent transaction is aborted, commands ignored until end of transaction blockAn earlier statement in this transaction failed; every statement after it is refused. The real error is the one before this
40001serialization_failurecould not serialize access due to concurrent updateA concurrent transaction conflicted under strict isolation. Retry the transaction
40P01deadlock_detecteddeadlock detectedTwo transactions waited on each other. Retry, and fix the lock order — see PostgreSQL Ch 15
55P03lock_not_availablecanceling statement due to lock timeoutThe statement waited longer than lock_timeout for a lock
57014query_canceledcanceling statement due to statement timeoutThe query ran longer than statement_timeout
53300too_many_connectionssorry, too many clients already / remaining connection slots are reserved…The database's connection limit is reached — usually too many app instances, each with its own pool
42P01undefined_tablerelation "orders" does not existWrong table name, wrong schema, or a migration that has not run in this environment
42703undefined_columncolumn "status" does not existSame — a typo, or a missing migration
28P01invalid_passwordpassword authentication failed for user "app"Wrong credentials for this environment
3D000invalid_catalog_namedatabase "app_prod" does not existWrong database name in the connection string
57P01admin_shutdownterminating connection due to administrator commandThe database restarted or failed over and dropped your connection

Some database errors come from the client library or pool, not from Postgres, so they have no SQLSTATE: timeout exceeded when trying to connect (the pool had no free connection in time — usually pool exhaustion), and Connection terminated unexpectedly (the server or network dropped the connection). These are covered in Chapter 9.


Part 8: From Code to Layer — The Reflex

The point of this chapter is one reflex: the code tells you which layer to look at.

You seeLook atNot at
ENOTFOUND, EAI_AGAIN, ERR_NAME_NOT_RESOLVEDDNS, hostnames, env vars, which network you are onYour business logic
ECONNREFUSEDIs the service running? Right port? Listening on the right interface?Firewalls (refused means you reached the machine)
ETIMEDOUTFirewalls, security groups, routing, an overloaded targetThe service's code
ECONNRESET, socket hang up, 502Crashes, restarts, proxies, keep-alive and idle timeoutsRequest validation
Certificate codesThe certificate, its chain, the hostname, the clockThe network
Exit 137 / OOMKilledContainer memory limit and the process's total memoryYour error handling
Exit 134 with "heap out of memory"JavaScript memory growth — leaks, huge payloadsThe container limit
Exit 143Normal shutdown — but check your app handles SIGTERM gracefullyCrashes
SQLSTATE 23xxxYour data and your logic (duplicates, missing references)The database server
SQLSTATE 40xxxConcurrency — retry, then reduce contentionQuery syntax
SQLSTATE 42xxxThe query and the schema — and whether migrations ranLoad
SQLSTATE 25P02The previous error in the same transactionThis statement

The Causal Chains

Self-test: predict each consequence before revealing it.
  • If you see ECONNREFUSED
  • If you see ETIMEDOUT
  • If you see ENOTFOUND
  • If you see occasional ECONNRESET on reused connections
  • If an HTTPS call works in the browser but fails from Node with UNABLE_TO_VERIFY_LEAF_SIGNATURE
  • If a container exits with code 137
  • If Node exits with 134 and "heap out of memory"
  • If you see current transaction is aborted, commands ignored
  • If Postgres says invalid input syntax for type uuid: "undefined"
  • If you see 23505 unique_violation on a create endpoint
  • If you see 40001 or 40P01
  • If you see Cannot set headers after they are sent to the client

The Rule You Keep Forever

A code is a precise statement about which layer failed and how. Read the code first, decide the layer, and only then open the code that lives in that layer.


Vocabulary Map

TermWhat it means
System call (syscall)A request from a program to the operating system — open, connect, listen, read, write
errnoThe number the OS returns when a syscall fails, with a symbolic name like ECONNREFUSED
err.codeThe machine-readable error identifier Node and libraries attach to errors
DNS resolutionTurning a hostname into an IP address
TCP connectOpening a connection to an IP and port
TCP reset (RST)An abrupt "this connection is closed" signal; surfaces as ECONNRESET
Connection refusedThe host answered but nothing listens on the port
Connection timeoutNothing answered within the time limit
Keep-aliveReusing one TCP connection for several HTTP requests
Idle timeoutHow long a side keeps an unused connection before closing it
TLS handshakeThe negotiation after TCP connect that verifies the certificate and sets up encryption
Certificate chainThe server's certificate plus the intermediate certificates linking it to a trusted root
Exit codeThe number a process reports when it ends; 0 is success
SignalAn OS message to a process — SIGTERM (please stop), SIGKILL (stop now), SIGINT (Ctrl+C)
OOM killerThe kernel or container runtime killing a process that exceeded its memory limit
OOMKilledKubernetes' status for a container killed for exceeding memory
CrashLoopBackOffKubernetes repeatedly restarting a crashing container with growing delays
Graceful shutdownHandling SIGTERM by finishing in-flight work before exiting
SQLSTATEPostgres' five-character error code; the first two characters are the class
Retryable errorA failure that may succeed if repeated unchanged, like 40001 or a transient network error

Practice Problems

Problem 1 — Refused or timed out? Your app cannot reach Redis. In one environment the error appears instantly as ECONNREFUSED; in another it appears after 10 seconds as ETIMEDOUT. For each, name two likely causes and the first command you would run.

Problem 2 — The container question. A Node API in a container is restarted every few hours. The orchestrator shows exit code 137. A teammate wants to raise --max-old-space-size. Is that the right first move? What evidence would tell you whether the problem is the JS heap or total process memory?

Problem 3 — Read the transaction. Logs show current transaction is aborted, commands ignored until end of transaction block thousands of times, all from one endpoint. What is the very next thing you search the logs for, and why?

Problem 4 — Retry or not? For each, say whether an automatic retry is correct and why: (a) 40P01 deadlock_detected; (b) 23505 unique_violation on POST /signup; (c) ECONNRESET on a GET; (d) ECONNRESET on POST /payments.

Problem 5 — Same error, two worlds. A partner's HTTPS API works in Chrome but your Node service gets UNABLE_TO_VERIFY_LEAF_SIGNATURE. Explain why the behaviour differs, and what you would tell the partner.


What's Coming Next

Most of your debugging traffic speaks HTTP, and HTTP has its own codes: 401, 403, 429, 502, 504. Chapter 4 decodes the status codes — and teaches the skill most engineers miss: working out which hop in the chain actually sent the status. A 502 from your load balancer and a 502 from your own code mean completely different things.