An encrypted connection is useful only when the client also knows which server is at the other end. HTTPS combines HTTP with TLS, including certificate verification that connects a server's public key to an expected identity. Beginners often silence a local certificate error by disabling verification, then accidentally carry that setting into production. This guide instead makes the local trust decision explicit. It assumes Node 24 with its normal crypto support. The main fixture includes a public demonstration certificate and private key for localhost. They are teaching data, valid from October 2026 to October 2036, and must never protect a deployed service.
Understand the two verification questions
A client first needs a trusted certificate chain and then a certificate matching the requested server identity. These are related but separate checks. A certificate signed by an untrusted authority does not become trusted because its hostname is correct. Conversely, a trusted certificate for another hostname does not authenticate the destination you intended to reach. Certificate validity dates and cryptographic checks also belong to the verification process. The machine's clock can therefore affect whether a connection succeeds.
For public production services, certificates normally chain to an authority in the client's trust configuration. For internal services, an organization may deliberately distribute its own authority certificate. Supply that trust through a supported client configuration rather than globally disabling verification. In the local example, the client receives the exact demonstration certificate through the ca option. This is a narrow test trust decision. Supplying ca explicitly affects the trust configuration for that client; do not assume it simply preserves every default authority while adding arbitrary new ones.
Separate the network address from server identity
A socket connects to an address and port, while a certificate identifies a hostname or an explicitly listed IP address. The fixture connects to 127.0.0.1 to keep traffic local but sets servername to localhost, matching the certificate's subject alternative name. Server Name Indication also lets a TLS server select a certificate when several domains share one address. An IP connection without the intended server name may fail hostname validation or select the wrong certificate on a hosted service.
Modern certificate identity should be represented through subject alternative names. A certificate for localhost does not automatically authorize every local IP spelling. Avoid replacing checkServerIdentity with a callback that returns success for every input. If a custom policy is necessary, call the standard check and preserve its failure before applying additional checks. The separate practice fixture demonstrates only hostname matching with a certificate-shaped object; it does not validate a chain, expiry, or a TLS handshake. Understanding that boundary prevents an offline identity check from being mistaken for complete verification.
Use HTTPS request and server lifecycles
https.createServer takes TLS options such as key and cert before the ordinary request listener. The handler can then use familiar HTTP response headers and status codes. A private key must correspond to the presented certificate. Production key loading should use controlled configuration or secret storage, not a hard-coded public example. Deployments often terminate TLS at a reverse proxy, leaving Node to serve an internal connection; the trust boundary and proxy configuration still need explicit design.
An HTTPS client has two streams of failures to consider: errors on the outgoing request and errors while reading the response body. Attach handlers to both when collecting a body. Check the HTTP status independently because a successful TLS handshake can still receive a 404 or 500. Limit response size in a real downloader. An Agent manages reusable connections; the fixture sets agent to false for a short one-request demonstration so shutdown is straightforward. In a long-lived application, connection reuse should be configured intentionally instead of creating a fresh handshake for every request.
Handle certificate errors without weakening policy
A certificate error is evidence to investigate: the wrong hostname, an expired certificate, an incomplete chain, an unknown internal authority, or an incorrect clock may be responsible. Treating all of them as an inconvenience removes the authentication that HTTPS is meant to provide. Do not set NODE_TLS_REJECT_UNAUTHORIZED to zero or use rejectUnauthorized: false to make a production request succeed. Install the intended trust authority and correct the server configuration instead.
Encryption does not validate the content returned by an authenticated service. Parse and validate response data after checking status and body limits. Redirects can also change the destination, so a client that follows them needs a destination policy at every hop. Certificate pinning can provide an additional trust restriction, but it introduces rotation and recovery requirements; it is not a casual replacement for standard verification. The fixture keeps the lesson focused on one local origin with explicit trust and hostname checks, closes the server in a finally block, and never contacts a public host.
Node.js documentation and version notes
Node.js HTTPS and TLS Verification Examples
Each example shows the code, the output it produces and what each part does.
Trusting a local certificate over HTTPS
Run this complete .mjs fixture on Node 24 during the certificate's stated validity interval. All certificate material is included, so no certificate generator or package is needed. The local server listens on an automatically selected port. The request trusts only the supplied demonstration certificate and requests the localhost identity while connecting to the loopback address. The response body and status are printed after the response ends. Failure rejects the promise and still reaches server cleanup. If your clock lies outside the demonstration validity interval, create a fresh local certificate with a localhost subject alternative name rather than weakening verification.
import https from 'node:https';
import { once } from 'node:events';
const key = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgf7Fz9A/QMFVcfRcQ
5aRb+i6e18U04OKaU9+5jX5D2FyhRANCAATM+5uJurgpOB7442knos9vgau2dI8u
54ZPYnXxhFtzbi5ic9H4wzj4eHP6hcLbwqZ0IB7ReSbSUrX1Ary+6n+e
-----END PRIVATE KEY-----`;
const cert = `-----BEGIN CERTIFICATE-----
MIIBkzCCATmgAwIBAgIUeduVid/rZBjT5QyO5r6Rbe4sQvkwCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MTAxMTEyMjAxNFoXDTM2MTAwODEy
MjAxNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEzPubibq4KTge+ONpJ6LPb4GrtnSPLueGT2J18YRbc24uYnPR+MM4+Hhz
+oXC28KmdCAe0Xkm0lK19QK8vup/nqNpMGcwHQYDVR0OBBYEFCaku0D0/buTNA/y
IZoPuqk9zdNbMB8GA1UdIwQYMBaAFCaku0D0/buTNA/yIZoPuqk9zdNbMBQGA1Ud
EQQNMAuCCWxvY2FsaG9zdDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA
MEUCICaa51RSxfCYljQps4haqNhCjV/zwqIwPJplexelOKvzAiEA3B+gla4L1L43
e0voOxsFwFnZZLj4wyBXl1aq/wx23gg=
-----END CERTIFICATE-----`;
const server = https.createServer({ key, cert }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' });
res.end('verified local service');
});
server.listen(0, '127.0.0.1');
await once(server, 'listening');
try {
const result = await new Promise((resolve, reject) => {
const req = https.get({ hostname: '127.0.0.1', port: server.address().port,
servername: 'localhost', ca: cert, agent: false }, res => {
let body = '';
res.setEncoding('utf8');
res.on('data', chunk => { body += chunk; });
res.on('error', reject);
res.on('end', () => resolve({ status: res.statusCode, body }));
});
req.on('error', reject);
});
console.log(result.status, result.body);
} finally {
await new Promise(resolve => server.close(resolve));
}200 verified local serviceNode.js HTTPS and TLS Verification in Real Projects
Test hostname matching with checkServerIdentity
Test hostname policy separately before working on certificate trust. The exercise supplies a certificate-shaped object whose subject alternative names cover one exact host and a wildcard domain. Predict which candidate names match, then run the standard checkServerIdentity function. A wildcard covers one label at the documented position; it is not a promise that every deeper subdomain matches. The code prints stable match or mismatch labels rather than entire error objects, whose descriptive details may differ across releases. This is a fully coded identity exercise, but its object is not a real certificate and its results must not be described as proof of certificate trust.
import { checkServerIdentity } from 'node:tls';
const certificate = { subjectaltname: 'DNS:api.example.test, DNS:*.shop.example.test' };
for (const hostname of ['api.example.test', 'a.shop.example.test',
'deep.a.shop.example.test', 'other.example.test']) {
const error = checkServerIdentity(hostname, certificate);
console.log(hostname, error ? 'mismatch' : 'match');
}api.example.test match
a.shop.example.test match
deep.a.shop.example.test mismatch
other.example.test mismatchNode.js HTTPS and TLS Verification FAQ
Why does the client use both hostname and servername?
hostname supplies the network destination in this fixture, while servername supplies the TLS identity and SNI name. Loopback avoids external DNS and keeps the test local. The certificate lists localhost, so the requested TLS identity must match that name. A normal URL-based request usually derives these choices from its hostname. Explicit separation is useful for controlled tests, but should not conceal a mismatch in a production service configuration.
Can I deploy the certificate from this example?
No. Its private key is public instructional data. Anyone reading the page has it, so it cannot authenticate a confidential production key holder. Use certificates and private keys generated and managed for your own service, with a renewal process and restricted access. The fixture deliberately provides complete material so learners can run an isolated example without disabling verification. Its stated dates are another limitation to check before reuse even for local teaching.
Does checkServerIdentity verify the whole certificate?
No. It checks whether the presented identity matches a hostname using the certificate information supplied to it. A TLS connection also verifies certificate signatures, trust, validity, and handshake details. The offline practice exercise isolates name matching for learning. Do not accept a network peer merely because an object passed this function; retain the normal TLS verification and use any custom checks as additional restrictions rather than blanket replacements.
Does an HTTPS status 500 mean TLS failed?
No. A 500 response can arrive over a fully authenticated encrypted connection. TLS success establishes the transport and peer identity; HTTP status reports the server's application result. Handle both layers separately. A request error may occur before an HTTP response exists, whereas an unsuccessful status still has headers and potentially a useful bounded response body. Report the correct layer so operators investigate the actual cause.
Node.js HTTPS and TLS Verification Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Why does the complete fixture trust its self-signed demonstration certificate?
Node.js HTTPS and TLS Verification Quiz
2 questions to check what you learned.
Which result proves only a hostname match?
- A successful offline checkServerIdentity call
- A complete verified TLS handshake with a trusted certificate
- An HTTPS request that verified its chain and hostname
What is the appropriate response to an unknown internal certificate authority?
- Configure the intended authority as trusted for the client
- Set rejectUnauthorized to false for every request
- Ignore the error if the response status would be 200