HTTPS er ikke lenger valgfritt. Over 95 prosent av all sidelasting i Chrome skjer i dag over kryptert tilkobling, ifølge Googles Transparency Report, og nettlesere markerer rene HTTP-sider som «ikke sikre». Samtidig kortes levetiden på sertifikater dramatisk ned: CA/Browser Forum vedtok i 2025 (ballot SC-081) at maksimal gyldighet for offentlige TLS-sertifikater faller til 200 dager fra mars 2026, 100 dager fra mars 2027 og bare 47 dager fra mars 2029. Manuell fornyelse er død. Denne veiledningen viser deg steg for steg hvordan du setter opp en produksjonsklar HTTPS-server i Node.js med TLS 1.3, automatisk sertifikatfornyelse og riktige sikkerhetshoder.
Du trenger ikke å være kryptograf for å følge med. Du trenger en Linux-server, Node.js og rundt 40 minutter. Vi bruker OpenSSL til lokale testsertifikater, https.createServer() i Node.js til selve serveren, og Let’s Encrypt med certbot til ekte sertifikater i produksjon. Til slutt har du et komplett, fungerende prosjekt med karakteren A på SSL Labs.
Hvorfor HTTPS og TLS 1.3 er standarden i 2026
HTTPS er HTTP pakket inn i TLS (Transport Layer Security). TLS sørger for tre ting på én gang: konfidensialitet (ingen kan lese trafikken), integritet (ingen kan endre den underveis) og autentisering (du snakker med riktig server). Uten TLS kan en angriper på samme nettverk lese passord, kapre sesjoner og injisere skadelig kode. Med TLS 1.3 er disse angrepene praktisk talt stengt ute, forutsatt at du konfigurerer serveren riktig.
TLS 1.3 ble standardisert i RFC 8446 og er nå den dominerende versjonen på nettet. Den fjernet alle de svake byggeklossene fra TLS 1.2: RSA-nøkkelutveksling, CBC-modus, RC4, SHA-1 og kompresjon. Resultatet er et protokollag som er både raskere og enklere å sette opp riktig. I TLS 1.3 forhandles ikke nøkkelutveksling lenger gjennom navnet på chifferpakken, så du står igjen med bare tre godkjente pakker i stedet for dusinvis av risikable kombinasjoner.
Den store driveren i 2026 er kortere sertifikatlevetid. Apple foreslo opprinnelig 47-dagers sertifikater, og CA/Browser Forum vedtok en gradvis nedtrapping. Tabellen under viser tidslinjen du må planlegge for. Poenget er enkelt: alt må automatiseres. En server som er avhengig av at noen husker å forny et sertifikat én gang i året, kommer til å gå ned.
| Dato | Maks gyldighet | Maks gjenbruk av domenevalidering | Konsekvens |
|---|---|---|---|
| Før mars 2026 | 398 dager | 398 dager | Årlig fornyelse mulig |
| 15. mars 2026 | 200 dager | 200 dager | Halvårlig fornyelse |
| 15. mars 2027 | 100 dager | 100 dager | Kvartalsvis fornyelse |
| 15. mars 2029 | 47 dager | 10 dager | Full automatisering påkrevd |
Vil du forstå de grunnleggende begrepene bak låsen i adresselinjen, har vi en egen forklaring i HTTPS og TLS: slik beskyttes forbindelsen din på nett. Denne artikkelen er den praktiske oppfølgeren: vi bygger faktisk serveren.
Slik fungerer TLS 1.3-håndtrykket
Før koden er det verdt å forstå hvorfor TLS 1.3 er raskere. Håndtrykket er prosessen der klient og server blir enige om nøkler. I TLS 1.2 tar et fullt håndtrykk to rundturer (2-RTT): klienten sier hei, serveren svarer, klienten sender nøkkelmateriale, serveren bekrefter. Først da kan applikasjonsdata flyte. På en forbindelse med 50 millisekunder rundturstid betyr det rundt 100 millisekunder forsinkelse før første byte med innhold.
TLS 1.3 kollapser dette til én rundtur (1-RTT). Klienten gjetter hvilken nøkkelutvekslingsgruppe serveren støtter og sender nøkkelmateriale allerede i første melding. På samme 50 ms-forbindelse er håndtrykket nede i rundt 50 millisekunder. For gjenopptatte sesjoner kan TLS 1.3 til og med bruke 0-RTT, der de første applikasjonsdataene sendes sammen med håndtrykket. Tabellen oppsummerer forskjellen.
| Egenskap | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Fullt håndtrykk | 2-RTT | 1-RTT |
| Gjenopptatt sesjon | 1-RTT | 0-RTT eller 1-RTT |
| Håndtrykk ved 50 ms RTT | ~100 ms | ~50 ms |
| Godkjente chifferpakker | Dusinvis | 3 |
| RSA-nøkkelutveksling | Tillatt | Fjernet |
| Forward secrecy | Valgfritt | Obligatorisk |
«Forward secrecy» betyr at hver sesjon får en unik, midlertidig nøkkel. Selv om en angriper senere stjeler serverens private nøkkel, kan de ikke dekryptere gammel trafikk de har spilt inn. I TLS 1.3 er dette ikke valgfritt lenger, det er bakt inn i protokollen. Det er en av grunnene til at vi tvinger fram TLS 1.3 senere i veiledningen.
Forutsetninger og versjoner
Versjoner betyr noe for TLS. Eldre Node.js- og OpenSSL-bygg mangler full TLS 1.3-støtte og post-kvante-nøkkelutveksling. Bruk minst Node.js 24 LTS, som er den anbefalte produksjonsbasen midt i 2026 med sikkerhetsstøtte fram til 30. april 2028. Node.js 24 pakker en moderne OpenSSL 3.x-gren, som er det du trenger for både TLS 1.3 og hybrid post-kvante-utveksling.
| Komponent | Minimumsversjon | Sjekk med | Merknad |
|---|---|---|---|
| Node.js | 24 LTS (24.16.0+) | node -v | Unngå «Current»-linjer i produksjon |
| OpenSSL (i Node) | 3.x | node -p process.versions.openssl | Følger med Node.js |
| OpenSSL (CLI) | 3.x | openssl version | Til testsertifikater |
| certbot | 3.x | certbot --version | Til Let’s Encrypt |
| Operativsystem | Ubuntu 24.04 LTS | lsb_release -a | Eller tilsvarende Linux |
Du trenger også et domenenavn som peker mot serveren din (en A-post mot serverens IP) for å hente et ekte Let’s Encrypt-sertifikat senere. Til de første stegene holder det med localhost og et selvsignert sertifikat. Sjekk versjonene dine før du går videre:
$ node -v
v24.16.0
$ node -p process.versions.openssl
3.5.1
$ openssl version
OpenSSL 3.5.1 1 Jul 2025
$ certbot --version
certbot 3.2.0
Steg 1: Prosjektoppsett
Lag en ren prosjektmappe og initialiser et Node.js-prosjekt. Vi holder oss til Nodes innebygde https– og tls-moduler i kjernen, slik at du forstår hva som faktisk skjer, men legger til Express for et realistisk applikasjonslag.
$ mkdir https-tls-node && cd https-tls-node
$ npm init -y
$ npm install express helmet
$ mkdir certs src
$ touch src/server.js
Mappen certs/ holder nøkler og sertifikater. Legg den til i .gitignore umiddelbart. Private nøkler skal aldri inn i versjonskontroll, og en lekket nøkkel er en av de vanligste årsakene til kompromitterte servere.
$ echo "certs/" >> .gitignore
$ echo "node_modules/" >> .gitignore
Steg 2: Lag et selvsignert sertifikat med OpenSSL
For lokal utvikling lager vi et selvsignert sertifikat med OpenSSL. Et selvsignert sertifikat er ikke klarert av nettlesere (du får en advarsel), men det krypterer forbindelsen helt likt et ekte sertifikat, og det er perfekt til testing. Kommandoen under lager en privat nøkkel og et sertifikat som er gyldig i 365 dager, med moderne elliptisk kurve (P-256) i stedet for treg RSA.
$ openssl req -x509 -newkey ec \
-pkeyopt ec_paramgen_curve:prime256v1 \
-keyout certs/key.pem \
-out certs/cert.pem \
-days 365 -nodes \
-subj "/C=NO/ST=Oslo/L=Oslo/O=Dev/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Flagget -nodes betyr «no DES», altså at den private nøkkelen ikke krypteres med passord. Det er greit lokalt, men i produksjon henter vi nøkkelen fra Let’s Encrypt i stedet. subjectAltName er kritisk: moderne nettlesere ignorerer feltet CN og krever at vertsnavnet står i SAN. Glemmer du dette, får du feilen ERR_CERT_COMMON_NAME_INVALID. Bekreft at sertifikatet ble laget riktig:
$ openssl x509 -in certs/cert.pem -noout -text | grep -A1 "Subject Alternative Name"
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
Steg 3: Din første HTTPS-server i Node.js
Nå bygger vi den enkleste mulige HTTPS-serveren. Vi leser nøkkel og sertifikat fra disk og sender dem inn i https.createServer(). Legg merke til at vi bruker fs.readFileSync ved oppstart, ikke ved hver forespørsel: filene leses én gang og holdes i minnet.
// src/server.js
import https from 'node:https';
import fs from 'node:fs';
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hei fra en kryptert HTTPS-tilkobling.');
});
const options = {
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem'),
};
https.createServer(options, app).listen(8443, () => {
console.log('HTTPS-server kjorer pa https://localhost:8443');
});
Sett "type": "module" i package.json for å bruke import. Start serveren med node src/server.js og åpne https://localhost:8443. Nettleseren advarer om sertifikatet (fordi det er selvsignert), men forbindelsen er kryptert. Tester du med curl, ser du resultatet av håndtrykket:
$ curl -k -v https://localhost:8443 2>&1 | grep -i "SSL connection"
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
Flagget -k ber curl godta det selvsignerte sertifikatet. Legg merke til at Node.js allerede forhandlet fram TLS 1.3 og en sterk chifferpakke uten at vi ba om det. Standardinnstillingene i Node.js 24 er gode, men i neste steg låser vi dem fast slik at ingen klient kan presse forbindelsen ned til en svakere protokoll.
Steg 4: Tving fram TLS 1.3 og velg chifferpakker
Standardinnstillingen i Node.js tillater fortsatt TLS 1.2 for bakoverkompatibilitet. For en ny tjeneste i 2026 vil du som regel kreve TLS 1.3 og avvise alt eldre. Det gjør du med minVersion. Vil du beholde TLS 1.2 for noen få gamle klienter, setter du minVersion: 'TLSv1.2' i stedet og definerer en streng chifferliste.
const options = {
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem'),
// Krev TLS 1.3, avvis alt eldre
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
// De tre godkjente TLS 1.3-pakkene
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
].join(':'),
honorCipherOrder: true,
};
De tre pakkene er de eneste TLS 1.3 definerer. TLS_AES_256_GCM_SHA384 er førstevalget på maskiner med AES-akselerasjon (nesten alle moderne CPU-er). TLS_CHACHA20_POLY1305_SHA256 er raskere på enheter uten slik akselerasjon, typisk eldre mobiler. honorCipherOrder: true betyr at serveren bestemmer rekkefølgen, ikke klienten. Vil du støtte begge TLS-versjoner samtidig, kan du la Mozilla generere en trygg konfigurasjon for deg via deres SSL Configuration Generator. Verifiser at serveren nå avviser TLS 1.2:
$ curl -k --tlsv1.2 --tls-max 1.2 https://localhost:8443
curl: (35) error:0A00010B:SSL routines::wrong version number
Steg 5: Omdiriger all HTTP-trafikk til HTTPS
En HTTPS-server alene hjelper lite hvis brukerne fortsatt kan nå deg over vanlig HTTP. Du må lytte på port 80 og omdirigere alt til port 443 med statuskode 301 (permanent flytting). Dette er også det Let’s Encrypt bruker til å validere domenet ditt, så porten må være åpen.
import http from 'node:http';
// Egen liten server kun for omdirigering
http.createServer((req, res) => {
const host = req.headers.host?.split(':')[0] ?? 'localhost';
res.writeHead(301, {
Location: `https://${host}${req.url}`,
});
res.end();
}).listen(80, () => {
console.log('HTTP-omdirigering aktiv pa port 80');
});
Bruk 301, ikke 302. En 302 (midlertidig) blir ikke bufret av nettlesere, så de prøver HTTP på nytt hver gang. En 301 forteller nettleseren at den alltid skal bruke HTTPS for dette domenet. Kombinert med HSTS i neste steg betyr det at brukeren nesten aldri sender en ukryptert forespørsel etter første besøk. Porter under 1024 krever rotrettigheter; i produksjon kjører du heller bak en reversproxy eller gir Node-prosessen CAP_NET_BIND_SERVICE.
Steg 6: HSTS og sikkerhetshoder med Helmet
HSTS (HTTP Strict Transport Security) er et svar-hode som ber nettleseren om å alltid bruke HTTPS for domenet ditt, selv om brukeren skriver http://. Det stenger en hel klasse angrep der en angriper avskjærer den aller første forespørselen. Vi bruker Helmet, som setter HSTS og en rekke andre sikkerhetshoder med fornuftige standardverdier.
import helmet from 'helmet';
app.use(helmet({
hsts: {
maxAge: 63072000, // 2 ar i sekunder
includeSubDomains: true,
preload: true,
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
},
}));
Verdien maxAge: 63072000 er to år, som er kravet for å komme på HSTS-preloadlisten. Preloadlisten er bakt inn i nettleserne selv, slik at de bruker HTTPS for domenet ditt allerede før første besøk. Når du er sikker på at hele domenet kjører HTTPS uten unntak, kan du sende det inn på hstspreload.org. Vær forsiktig: preload er vanskelig å reversere raskt, så test grundig først. Sjekk at hodet faktisk sendes:
$ curl -kI https://localhost:8443 | grep -i strict
strict-transport-security: max-age=63072000; includeSubDomains; preload
Steg 7: Ekte sertifikat med Let’s Encrypt og certbot
Selvsignerte sertifikater duger til testing, men i produksjon trenger du et sertifikat fra en klarert myndighet. Let’s Encrypt er den dominerende gratis sertifikatmyndigheten, med flere hundre millioner aktive sertifikater, og utsteder dem helautomatisk via ACME-protokollen. Verktøyet certbot snakker ACME for deg. Installer det og hent et sertifikat for domenet ditt:
$ sudo apt update && sudo apt install certbot
$ sudo certbot certonly --standalone \
-d eksempel.no -d www.eksempel.no \
--email [email protected] \
--agree-tos --no-eff-email
Modusen --standalone lar certbot starte sin egen lille webserver på port 80 for å bevise at du kontrollerer domenet (HTTP-01-utfordringen). Derfor må du stoppe din egen server på port 80 mens dette kjører, eller bruke --webroot i stedet. Når det er ferdig, ligger filene her:
/etc/letsencrypt/live/eksempel.no/fullchain.pem # sertifikat + mellomledd
/etc/letsencrypt/live/eksempel.no/privkey.pem # privat nokkel
Bruk fullchain.pem, ikke cert.pem. Fullkjeden inneholder både ditt sertifikat og Let’s Encrypts mellomsertifikat, slik at klienter kan bygge hele tillitskjeden. Glemmer du mellomleddet, fungerer siden i Chrome (som bufrer mellomsertifikater) men feiler i mange andre klienter. Pek serveren mot de nye filene:
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/fullchain.pem'),
minVersion: 'TLSv1.3',
};
Steg 8: Automatisk fornyelse (47-dagers kravet)
Med sertifikatlevetider på vei mot 47 dager er automatisk fornyelse ikke en luksus, men en forutsetning. Certbot installerer som regel en systemd-timer som fornyer automatisk. Sjekk at den er aktiv og test en tørrkjøring:
$ sudo systemctl list-timers | grep certbot
$ sudo certbot renew --dry-run
Et problem gjenstår: når certbot fornyer sertifikatet, leser ikke Node.js de nye filene automatisk. Du har to valg. Det enkleste er en deploy-hook som restarter tjenesten etter fornyelse. Det mer elegante er å laste sertifikatet på nytt uten nedetid med setSecureContext. Her er deploy-hook-varianten:
$ sudo certbot renew \
--deploy-hook "systemctl reload min-node-app"
For null nedetid kan du i stedet overvåke sertifikatfilen og bytte ut sikkerhetskonteksten i farten. Da slipper klientene som er midt i en forespørsel å bli kastet ut:
import tls from 'node:tls';
const server = https.createServer(options, app);
// Bytt sertifikat uten omstart nar filen endres
fs.watch('/etc/letsencrypt/live/eksempel.no/', () => {
const ctx = tls.createSecureContext({
key: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/fullchain.pem'),
});
server.setSecureContext(ctx);
console.log('Sertifikat lastet pa nytt uten nedetid');
});
Steg 9: Sesjonsgjenopptak og ytelse
Et fullt TLS-håndtrykk koster CPU og en rundtur. Sesjonsgjenopptak lar en klient som har vært innom før hoppe over deler av håndtrykket. I TLS 1.3 skjer dette med «session tickets». Node.js håndterer dette automatisk, men i en klynge med flere prosesser må alle dele samme nøkkel for tickets, ellers kan ikke en klient gjenoppta en sesjon på en annen prosess.
import crypto from 'node:crypto';
// Delt ticket-nokkel pa tvers av prosesser (48 byte)
const ticketKeys = crypto.randomBytes(48);
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/fullchain.pem'),
minVersion: 'TLSv1.3',
ticketKeys, // samme nokkel i alle arbeidsprosesser
sessionTimeout: 300, // gjenopptak gyldig i 5 minutter
};
Pass på 0-RTT. Det er fristende fordi det fjerner all håndtrykksforsinkelse, men 0-RTT-data er sårbare for replay-angrep: en angriper kan spille av den samme forespørselen flere ganger. Bruk det aldri på forespørsler som endrer tilstand (POST, PUT, DELETE). Node.js har 0-RTT av som standard, og det bør du la det være med mindre du vet nøyaktig hva du gjør. OCSP-stapling, der serveren selv vedlegger et ferskt tilbakekallingssvar, var tidligere viktig, men med korte sertifikatlevetider er den operasjonelle verdien redusert. Fokuser heller energien på automatisk fornyelse.
Steg 10: Post-kvante TLS med X25519MLKEM768
En kvantedatamaskin som er kraftig nok vil kunne knekke dagens nøkkelutveksling og dekryptere trafikk angripere lagrer i dag («harvest now, decrypt later»). Som svar ruller Chrome og Cloudflare i 2025 ut en hybrid nøkkelutveksling kalt X25519MLKEM768, som kombinerer den klassiske X25519-kurven med den kvanteresistente ML-KEM-768 (tidligere Kyber). Knekker noen den ene, beskytter den andre fortsatt.
Med en moderne OpenSSL 3.x-gren i Node.js 24 kan du be om denne gruppen via ecdhCurve. Dette er fortsatt nytt interoperabilitetsarbeid, så ikke gjør det til et hardt krav som avviser eldre klienter ennå. Tilby det som et alternativ:
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/fullchain.pem'),
minVersion: 'TLSv1.3',
// Hybrid post-kvante forst, deretter klassisk fallback
ecdhCurve: 'X25519MLKEM768:X25519:prime256v1',
};
Klienter som forstår hybrid-gruppen bruker den; resten faller pent tilbake til X25519. Du får framtidssikring uten å bryte noe. Vil du dykke dypere i hvorfor dette haster, har vi en bredere gjennomgang av digitale signaturer og asymmetriske nøkler.
Steg 11: Komplett, fungerende prosjekt
Her er hele serveren satt sammen: HTTP-omdirigering, HTTPS med TLS 1.3, sikkerhetshoder, sesjonsgjenopptak, hybrid post-kvante-utveksling og varm omlasting av sertifikat. Dette er et realistisk utgangspunkt for produksjon.
// src/server.js (komplett)
import https from 'node:https';
import http from 'node:http';
import tls from 'node:tls';
import fs from 'node:fs';
import crypto from 'node:crypto';
import express from 'express';
import helmet from 'helmet';
const DOMAIN = process.env.DOMAIN ?? 'eksempel.no';
const CERT_DIR = `/etc/letsencrypt/live/${DOMAIN}`;
const app = express();
app.use(helmet({
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
}));
app.get('/', (req, res) => {
res.send('Sikker HTTPS-tilkobling med TLS 1.3.');
});
app.get('/health', (req, res) => res.json({ status: 'ok' }));
function loadCerts() {
return {
key: fs.readFileSync(`${CERT_DIR}/privkey.pem`),
cert: fs.readFileSync(`${CERT_DIR}/fullchain.pem`),
};
}
const options = {
...loadCerts(),
minVersion: 'TLSv1.3',
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
].join(':'),
honorCipherOrder: true,
ecdhCurve: 'X25519MLKEM768:X25519:prime256v1',
ticketKeys: crypto.randomBytes(48),
sessionTimeout: 300,
};
const server = https.createServer(options, app);
// Varm omlasting nar certbot fornyer
fs.watch(CERT_DIR, () => {
try {
server.setSecureContext(tls.createSecureContext(loadCerts()));
console.log('Sertifikat lastet pa nytt');
} catch (err) {
console.error('Klarte ikke laste sertifikat:', err.message);
}
});
server.listen(443, () => console.log(`HTTPS aktiv pa https://${DOMAIN}`));
// Omdiriger HTTP til HTTPS
http.createServer((req, res) => {
const host = req.headers.host?.split(':')[0] ?? DOMAIN;
res.writeHead(301, { Location: `https://${host}${req.url}` });
res.end();
}).listen(80);
Kjør serveren bak en prosessovervåker som systemd eller PM2, slik at den starter på nytt ved krasj og ved omstart av serveren. Sett DOMAIN som miljøvariabel, så kan samme kode kjøre i flere miljøer uten endring.
Steg 12: Verifiser med SSL Labs og curl
Test alltid den ferdige serveren utenfra. Det enkleste er Qualys SSL Labs-test, som gir karakter fra A+ til F og påpeker svakheter. Mål er A eller A+. Lokalt verifiserer du protokoll og chiffer med curl:
$ curl -vI https://eksempel.no 2>&1 | grep -iE "SSL connection|HTTP/"
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
HTTP/2 200
$ echo | openssl s_client -connect eksempel.no:443 \
-tls1_3 2>/dev/null | grep -E "Protocol|Cipher"
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
For en A+-karakter trenger du som regel TLS 1.3, HSTS med lang maxAge, ingen støtte for TLS 1.0/1.1, et gyldig sertifikat med full kjede og forward secrecy. Følger du stegene over, har du alt dette. Kjør testen på nytt etter hver større endring i TLS-konfigurasjonen.
6 vanlige fallgruver
- Manglende subjectAltName. Nettlesere ignorerer
CNog krever vertsnavnet i SAN. Uten det feiler sertifikatet medERR_CERT_COMMON_NAME_INVALID, selv om alt annet er riktig. - Bruker cert.pem i stedet for fullchain.pem. Da mangler mellomsertifikatet. Det fungerer i Chrome, men feiler i Safari, Firefox og mange API-klienter som ikke bufrer mellomledd.
- Glemmer å forny. Med 47-dagers sertifikater på vei er manuell fornyelse en garantert nedetid. Sett opp certbots timer og test den med
--dry-runfør den trengs. - Private nøkler i Git. En lekket
privkey.pembetyr at hele sikkerheten er borte. Leggcerts/i.gitignorefra første commit, og roter nøkkelen umiddelbart hvis den noen gang er pushet. - 0-RTT på skrivende endepunkter. Tidlig data kan spilles av på nytt. Aktiver aldri 0-RTT for POST, PUT eller DELETE. La det stå av med mindre du har en idempotent leseoperasjon.
- Tillater fortsatt TLS 1.0/1.1. Disse er foreldet og senker SSL Labs-karakteren din. Sett
minVersiontil minstTLSv1.2, helstTLSv1.3.
Feilsøking: 8 vanlige problemer
| Feilmelding / symptom | Sannsynlig årsak | Løsning |
|---|---|---|
EACCES: permission denied :443 | Porter under 1024 krever rot | Bruk setcap eller reversproxy |
ERR_CERT_COMMON_NAME_INVALID | Vertsnavn mangler i SAN | Legg til subjectAltName |
ERR_CERT_AUTHORITY_INVALID | Selvsignert eller ufullstendig kjede | Bruk fullchain.pem |
wrong version number | Klient prøver feil TLS-versjon | Sjekk minVersion/maxVersion |
ERR_SSL_PROTOCOL_ERROR | HTTP sendt til HTTPS-port | Bruk https://, ikke http:// |
Curl: (60) SSL certificate problem | Klient stoler ikke på CA | -k lokalt, ekte sertifikat i prod |
| NET::ERR_CERT_DATE_INVALID | Sertifikat utløpt | Forny med certbot renew |
| Fornyelse feiler på port 80 | Port 80 opptatt eller blokkert | Stopp tjenesten eller bruk --webroot |
De fleste TLS-problemer faller i én av tre kategorier: feil med sertifikatkjeden, feil med protokollversjon eller feil med rettigheter på lave porter. Når noe svikter, start med openssl s_client -connect domene:443. Den viser hele kjeden, protokollen og chifferet, og avslører som regel årsaken på sekunder.
Avanserte tips for produksjon
Kjør bak en reversproxy
I de fleste produksjonsoppsett terminerer du TLS i nginx, Caddy eller en lastbalanserer, ikke i Node.js direkte. Det gir deg sentralisert sertifikathåndtering, enklere lastbalansering og bedre beskyttelse mot tilkoblingsangrep. Caddy henter og fornyer til og med Let’s Encrypt-sertifikater helautomatisk. Da kjører Node-appen din ren HTTP på en intern port, og proxyen står for HTTPS utad. Husk å sette app.set('trust proxy', 1) slik at Express stoler på X-Forwarded-Proto-hodet.
Overvåk utløp før det smerter
Med korte levetider er et utløpt sertifikat den mest sannsynlige nedetidsårsaken. Sett opp overvåking som varsler deg minst sju dager før utløp. En enkel cron-jobb med openssl x509 -enddate -noout som sammenligner mot dagens dato holder, men dedikerte tjenester som overvåker sertifikatutløp er enda tryggere. Logg også hver vellykkede fornyelse, slik at du vet at automatikken faktisk kjører.
Test med SSL Labs i CI
Du kan kjøre testssl.sh eller SSL Labs sitt API som en del av utrullingen. Bryt bygget hvis karakteren faller under A. Da fanger du en svak chiffer eller en glemt protokollnedgradering før den når produksjon. Kombiner dette med en automatisk sjekk av at HSTS-hodet og omdirigeringen fra HTTP fortsatt fungerer.
Forstå sertifikatkjeden og tillitsankeret
Et SSL-sertifikat (riktigere: TLS-sertifikat) er ikke en isolert fil. Det er en lenke i en kjede som ender i et rotsertifikat nettleseren stoler på fra før. Når en klient kobler til HTTPS-serveren din, får den serverens sertifikat pluss ett eller flere mellomsertifikater. Klienten følger kjeden oppover til den treffer et rotsertifikat som ligger i operativsystemets eller nettleserens tillitslager. Finner den et slikt anker, er sertifikatet klarert. Finner den ikke, får brukeren en advarsel.
Dette forklarer hvorfor fullchain.pem er så viktig. Rotsertifikatet ligger allerede hos klienten, men mellomsertifikatet gjør det ikke alltid. Sender du bare ditt eget sertifikat uten mellomleddet, kan ikke klienten bygge kjeden helt opp til roten. Chrome skjuler ofte problemet fordi den bufrer mellomsertifikater den har sett før, men Safari, eldre Android og mange programmatiske klienter feiler hardt. Du kan inspisere hele kjeden serveren faktisk sender med OpenSSL:
$ echo | openssl s_client -connect eksempel.no:443 \
-showcerts 2>/dev/null | grep -E "s:|i:"
0 s:CN=eksempel.no
i:C=US, O=Let's Encrypt, CN=R13
1 s:C=US, O=Let's Encrypt, CN=R13
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
Her ser du to ledd: serverens eget sertifikat (nivå 0), signert av Let’s Encrypts mellomsertifikat R13, som igjen er signert av roten ISRG Root X1. Roten er selvsignert og ligger i tillitslageret. Forstår du denne strukturen, blir de fleste sertifikatfeil enkle å diagnostisere: nesten alltid mangler et ledd, er utløpt, eller har feil vertsnavn. Vil du grave dypere i hvordan signaturer og tillit henger sammen matematisk, anbefaler vi gjennomgangen vår av SHA-256 og digitale avtrykk, som er byggeklossen under hver eneste sertifikatsignatur.
HTTP/2 og HTTP/3 over TLS
TLS handler ikke bare om sikkerhet, men også om hvilken protokoll som kjører oppå. Moderne nettlesere foretrekker HTTP/2 og HTTP/3, som multiplekser mange forespørsler over én forbindelse og fjerner «head-of-line blocking» fra HTTP/1.1. Hvilken versjon som velges, forhandles under TLS-håndtrykket via en utvidelse som heter ALPN (Application-Layer Protocol Negotiation). Det fine er at HTTP/2 alltid krever TLS i praksis, så når du først har HTTPS på plass, er du nesten klar.
Node.js har en egen http2-modul som snakker HTTP/2 med samme sertifikater. Du bytter ut https.createServer med http2.createSecureServer og setter allowHTTP1: true for å falle tilbake til HTTP/1.1 for klienter som ikke støtter HTTP/2:
import http2 from 'node:http2';
import fs from 'node:fs';
const server = http2.createSecureServer({
key: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/eksempel.no/fullchain.pem'),
minVersion: 'TLSv1.3',
allowHTTP1: true, // fall tilbake til HTTP/1.1 ved behov
});
server.on('stream', (stream) => {
stream.respond({ ':status': 200, 'content-type': 'text/plain' });
stream.end('Levert over HTTP/2 og TLS 1.3');
});
server.listen(443);
HTTP/3 går et skritt lenger og bytter ut TCP med QUIC, som kjører over UDP og har TLS 1.3 bakt direkte inn i transportlaget. Det fjerner den ekstra rundturen TCP-håndtrykket koster, slik at den totale oppkoblingstiden synker ytterligere. Node.js har eksperimentell QUIC-støtte, men i produksjon terminerer de fleste HTTP/3 i en reversproxy som nginx eller i en kantnode hos Cloudflare. Uansett protokoll over er det fortsatt TLS 1.3 som beskytter selve dataene, så konfigurasjonen vi har bygget opp gjelder fullt ut.
TLS 1.3 bak en nginx-reversproxy
De fleste norske og nordiske produksjonsoppsett kjører Node.js bak nginx. Da terminerer nginx TLS, og Node-appen lytter på en intern HTTP-port. Fordelen er sentralisert sertifikathåndtering og enklere skalering. Her er en minimal, men trygg nginx-konfigurasjon som kun tillater TLS 1.3, setter HSTS og sender trafikken videre til Node på port 3000:
server {
listen 443 ssl;
http2 on;
server_name eksempel.no;
ssl_certificate /etc/letsencrypt/live/eksempel.no/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/eksempel.no/privkey.pem;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name eksempel.no;
return 301 https://$host$request_uri;
}
Legg merke til X-Forwarded-Proto https. Uten dette hodet vet ikke Express at den opprinnelige forespørselen kom over HTTPS, og logikk som tvinger sikre cookies eller omdirigerer kan gå i loop. Sett derfor app.set('trust proxy', 1) i Node-appen. Med ssl_protocols TLSv1.3 avviser nginx alt eldre på samme måte som minVersion gjorde i Node. Test konfigurasjonen med nginx -t før du laster den på nytt, og verifiser resultatet med en SSL Labs-kjøring. Et oppsett som dette gir typisk karakteren A+ uten ekstra arbeid, så lenge sertifikatkjeden er komplett og fornyelsen er automatisert.
Gjensidig TLS (mTLS) for tjeneste-til-tjeneste
I vanlig HTTPS autentiserer bare serveren seg. Klienten sjekker serverens sertifikat, men serveren vet ikke hvem klienten er før et eventuelt innloggingssteg på applikasjonsnivå. For intern trafikk mellom tjenester, API-er med høye krav, eller en nullstillit-arkitektur, vil du ofte at begge parter beviser identiteten sin. Det kalles gjensidig TLS, eller mTLS, og det skjer i selve TLS-laget før en eneste byte applikasjonsdata sendes.
I Node.js slår du på mTLS med to alternativer: requestCert: true ber klienten om et sertifikat, og rejectUnauthorized: true avviser klienter som ikke kan vise et gyldig sertifikat signert av en CA du stoler på. Du oppgir den klarerte CA-en med ca:
const options = {
key: fs.readFileSync('certs/server-key.pem'),
cert: fs.readFileSync('certs/server-cert.pem'),
// Krev og verifiser klientsertifikat
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('certs/intern-ca.pem')],
minVersion: 'TLSv1.3',
};
https.createServer(options, (req, res) => {
const cert = req.socket.getPeerCertificate();
res.end(`Hei, klient: ${cert.subject.CN}`);
}).listen(8443);
Med dette på plass kan ingen koble til uten et gyldig klientsertifikat. Du henter klientens identitet fra req.socket.getPeerCertificate() og kan bruke CN eller andre felt til autorisasjon. mTLS er ryggraden i mange tjenestenett (service mesh) som Istio og Linkerd, der hver tjeneste får sitt eget kortlivede sertifikat og all intern trafikk er kryptert og autentisert. For et selvbygd oppsett trenger du en intern CA til å signere både server- og klientsertifikater, noe du kan sette opp med OpenSSL eller verktøy som cfssl.
Ofte stilte spørsmål
Trenger jeg HTTPS på en intern tjeneste?
Ja. Interne nettverk er ikke trygge. Angripere som først kommer inn beveger seg sideveis, og ukryptert intern trafikk gir dem passord og sesjoner gratis. Bruk gjerne en intern sertifikatmyndighet eller mTLS for tjeneste-til-tjeneste-trafikk, men ikke dropp krypteringen.
Hva er forskjellen på SSL og TLS?
SSL er forgjengeren til TLS og er fullstendig foreldet; selv SSL 3.0 er usikker. Når folk sier «SSL-sertifikat», mener de egentlig et TLS-sertifikat. Protokollen du faktisk bruker i 2026 er TLS 1.3, eventuelt TLS 1.2 for gamle klienter. Begrepet «SSL» henger igjen av historiske grunner.
Er Let’s Encrypt trygt nok for produksjon?
Ja. Et Let’s Encrypt-sertifikat gir nøyaktig samme kryptering som et betalt sertifikat; forskjellen ligger i validering og garantier, ikke i sikkerhet. Det brukes av flere hundre millioner nettsteder. Trenger du Extended Validation eller forsikring, kan en kommersiell CA være riktig, men for de fleste tjenester er Let’s Encrypt det opplagte valget.
Bør jeg slå på HSTS preload med en gang?
Nei, vent. Preload er bakt inn i nettleserne og er treg å reversere. Bekreft først at hele domenet, inkludert alle underdomener, fungerer feilfritt over HTTPS i flere uker. Send inn til preloadlisten først når du er helt sikker, ellers risikerer du at deler av siden blir utilgjengelig.
Hva er X25519MLKEM768, og må jeg bruke det nå?
Det er en hybrid nøkkelutveksling som kombinerer klassisk X25519 med den kvanteresistente ML-KEM-768. Chrome og Cloudflare rullet det ut i 2025 for å beskytte mot framtidige kvantedatamaskiner. Du må ikke kreve det ennå, men å tilby det som alternativ koster ingenting og gir framtidssikring. Les mer om temaet i vår oversikt over kryptografi og digital tillit.
Hvorfor faller SSL Labs-karakteren min til B?
De vanligste årsakene er støtte for TLS 1.0/1.1, svake chiffer, manglende forward secrecy eller en ufullstendig sertifikatkjede. Sett minVersion til TLSv1.2 eller høyere, bruk bare moderne chiffer og send fullchain.pem. Kjør testen på nytt, så ser du som regel karakteren stige til A.
Kan jeg bruke samme oppsett med Deno eller Bun?
Prinsippene er identiske, men API-et er litt annerledes. Både Deno og Bun har innebygd TLS-støtte og kan lese de samme PEM-filene fra Let’s Encrypt. Begrepene minVersion, chifferpakker og HSTS gjelder likt. Denne veiledningen fokuserer på Node.js fordi det fortsatt er det mest utbredte i produksjon.
Relatert innhold
- HTTPS og TLS: slik beskyttes forbindelsen din på nett
- scrypt passordhashing i Node.js: 11 steg
- SSH-nøkkel i Linux: sikker innlogging i 10 steg
- WireGuard VPN på Linux: 12 steg, 1472 Mbps
- Digitale signaturer: hashfunksjoner og asymmetriske nøkler
- Nettsikkerhet: datalekkasjer, passord, HTTPS og phishing




