{"id":88,"date":"2026-06-15T16:59:06","date_gmt":"2026-06-15T16:59:06","guid":{"rendered":"https:\/\/shattered.io\/no\/2026\/06\/15\/https-tls-13-nodejs\/"},"modified":"2026-06-16T08:45:08","modified_gmt":"2026-06-16T08:45:08","slug":"https-tls-13-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/no\/https-tls-13-nodejs\/","title":{"rendered":"HTTPS og TLS 1.3 i Node.js: 12 steg [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>HTTPS<\/strong> er ikke lenger valgfritt. Over 95 prosent av all sidelasting i Chrome skjer i dag over kryptert tilkobling, if\u00f8lge Googles Transparency Report, og nettlesere markerer rene HTTP-sider som \u00abikke sikre\u00bb. Samtidig kortes levetiden p\u00e5 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\u00f8d. Denne veiledningen viser deg steg for steg hvordan du setter opp en produksjonsklar <strong>HTTPS<\/strong>-server i Node.js med <strong>TLS<\/strong> 1.3, automatisk sertifikatfornyelse og riktige sikkerhetshoder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Du trenger ikke \u00e5 v\u00e6re kryptograf for \u00e5 f\u00f8lge med. Du trenger en Linux-server, Node.js og rundt 40 minutter. Vi bruker <strong>OpenSSL<\/strong> til lokale testsertifikater, <code>https.createServer()<\/code> i Node.js til selve serveren, og Let&#8217;s Encrypt med certbot til ekte sertifikater i produksjon. Til slutt har du et komplett, fungerende prosjekt med karakteren A p\u00e5 SSL Labs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"hvorfor-https-og-tls-1-3-er-standarden-i-2026\">Hvorfor HTTPS og TLS 1.3 er standarden i 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>HTTPS<\/strong> er HTTP pakket inn i <strong>TLS<\/strong> (Transport Layer Security). TLS s\u00f8rger for tre ting p\u00e5 \u00e9n 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\u00e5 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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3 ble standardisert i RFC 8446 og er n\u00e5 den dominerende versjonen p\u00e5 nettet. Den fjernet alle de svake byggeklossene fra TLS 1.2: RSA-n\u00f8kkelutveksling, CBC-modus, RC4, SHA-1 og kompresjon. Resultatet er et protokollag som er b\u00e5de raskere og enklere \u00e5 sette opp riktig. I TLS 1.3 forhandles ikke n\u00f8kkelutveksling lenger gjennom navnet p\u00e5 chifferpakken, s\u00e5 du st\u00e5r igjen med bare tre godkjente pakker i stedet for dusinvis av risikable kombinasjoner.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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\u00e5 planlegge for. Poenget er enkelt: alt m\u00e5 automatiseres. En server som er avhengig av at noen husker \u00e5 forny et sertifikat \u00e9n gang i \u00e5ret, kommer til \u00e5 g\u00e5 ned.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Dato<\/th><th>Maks gyldighet<\/th><th>Maks gjenbruk av domenevalidering<\/th><th>Konsekvens<\/th><\/tr><\/thead><tbody><tr><td>F\u00f8r mars 2026<\/td><td>398 dager<\/td><td>398 dager<\/td><td>\u00c5rlig fornyelse mulig<\/td><\/tr><tr><td>15. mars 2026<\/td><td>200 dager<\/td><td>200 dager<\/td><td>Halv\u00e5rlig fornyelse<\/td><\/tr><tr><td>15. mars 2027<\/td><td>100 dager<\/td><td>100 dager<\/td><td>Kvartalsvis fornyelse<\/td><\/tr><tr><td>15. mars 2029<\/td><td>47 dager<\/td><td>10 dager<\/td><td>Full automatisering p\u00e5krevd<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-block-table__caption\">Kilde: CA\/Browser Forum ballot SC-081 (vedtatt 2025).<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Vil du forst\u00e5 de grunnleggende begrepene bak l\u00e5sen i adresselinjen, har vi en egen forklaring i <a href=\"\/no\/2026\/06\/10\/https-og-tls\/\">HTTPS og TLS: slik beskyttes forbindelsen din p\u00e5 nett<\/a>. Denne artikkelen er den praktiske oppf\u00f8lgeren: vi bygger faktisk serveren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"slik-fungerer-tls-1-3-handtrykket\">Slik fungerer TLS 1.3-h\u00e5ndtrykket<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00f8r koden er det verdt \u00e5 forst\u00e5 hvorfor TLS 1.3 er raskere. H\u00e5ndtrykket er prosessen der klient og server blir enige om n\u00f8kler. I TLS 1.2 tar et fullt h\u00e5ndtrykk to rundturer (2-RTT): klienten sier hei, serveren svarer, klienten sender n\u00f8kkelmateriale, serveren bekrefter. F\u00f8rst da kan applikasjonsdata flyte. P\u00e5 en forbindelse med 50 millisekunder rundturstid betyr det rundt 100 millisekunder forsinkelse f\u00f8r f\u00f8rste byte med innhold.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3 kollapser dette til \u00e9n rundtur (1-RTT). Klienten gjetter hvilken n\u00f8kkelutvekslingsgruppe serveren st\u00f8tter og sender n\u00f8kkelmateriale allerede i f\u00f8rste melding. P\u00e5 samme 50 ms-forbindelse er h\u00e5ndtrykket nede i rundt 50 millisekunder. For gjenopptatte sesjoner kan TLS 1.3 til og med bruke 0-RTT, der de f\u00f8rste applikasjonsdataene sendes sammen med h\u00e5ndtrykket. Tabellen oppsummerer forskjellen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Egenskap<\/th><th>TLS 1.2<\/th><th>TLS 1.3<\/th><\/tr><\/thead><tbody><tr><td>Fullt h\u00e5ndtrykk<\/td><td>2-RTT<\/td><td>1-RTT<\/td><\/tr><tr><td>Gjenopptatt sesjon<\/td><td>1-RTT<\/td><td>0-RTT eller 1-RTT<\/td><\/tr><tr><td>H\u00e5ndtrykk ved 50 ms RTT<\/td><td>~100 ms<\/td><td>~50 ms<\/td><\/tr><tr><td>Godkjente chifferpakker<\/td><td>Dusinvis<\/td><td>3<\/td><\/tr><tr><td>RSA-n\u00f8kkelutveksling<\/td><td>Tillatt<\/td><td>Fjernet<\/td><\/tr><tr><td>Forward secrecy<\/td><td>Valgfritt<\/td><td>Obligatorisk<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-block-table__caption\">TLS 1.3 reduserer b\u00e5de forsinkelse og angrepsflate.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u00abForward secrecy\u00bb betyr at hver sesjon f\u00e5r en unik, midlertidig n\u00f8kkel. Selv om en angriper senere stjeler serverens private n\u00f8kkel, 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"forutsetninger-og-versjoner\">Forutsetninger og versjoner<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Versjoner betyr noe for TLS. Eldre Node.js- og OpenSSL-bygg mangler full TLS 1.3-st\u00f8tte og post-kvante-n\u00f8kkelutveksling. Bruk minst Node.js 24 LTS, som er den anbefalte produksjonsbasen midt i 2026 med sikkerhetsst\u00f8tte fram til 30. april 2028. Node.js 24 pakker en moderne OpenSSL 3.x-gren, som er det du trenger for b\u00e5de TLS 1.3 og hybrid post-kvante-utveksling.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Komponent<\/th><th>Minimumsversjon<\/th><th>Sjekk med<\/th><th>Merknad<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>24 LTS (24.16.0+)<\/td><td><code>node -v<\/code><\/td><td>Unng\u00e5 \u00abCurrent\u00bb-linjer i produksjon<\/td><\/tr><tr><td>OpenSSL (i Node)<\/td><td>3.x<\/td><td><code>node -p process.versions.openssl<\/code><\/td><td>F\u00f8lger med Node.js<\/td><\/tr><tr><td>OpenSSL (CLI)<\/td><td>3.x<\/td><td><code>openssl version<\/code><\/td><td>Til testsertifikater<\/td><\/tr><tr><td>certbot<\/td><td>3.x<\/td><td><code>certbot --version<\/code><\/td><td>Til Let&#8217;s Encrypt<\/td><\/tr><tr><td>Operativsystem<\/td><td>Ubuntu 24.04 LTS<\/td><td><code>lsb_release -a<\/code><\/td><td>Eller tilsvarende Linux<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-block-table__caption\">Anbefalt programvarestabel for HTTPS-veiledningen.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Du trenger ogs\u00e5 et domenenavn som peker mot serveren din (en A-post mot serverens IP) for \u00e5 hente et ekte Let&#8217;s Encrypt-sertifikat senere. Til de f\u00f8rste stegene holder det med <code>localhost<\/code> og et selvsignert sertifikat. Sjekk versjonene dine f\u00f8r du g\u00e5r videre:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node -v\nv24.16.0\n$ node -p process.versions.openssl\n3.5.1\n$ openssl version\nOpenSSL 3.5.1 1 Jul 2025\n$ certbot --version\ncertbot 3.2.0<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-1-prosjektoppsett\">Steg 1: Prosjektoppsett<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lag en ren prosjektmappe og initialiser et Node.js-prosjekt. Vi holder oss til Nodes innebygde <code>https<\/code>&#8211; og <code>tls<\/code>-moduler i kjernen, slik at du forst\u00e5r hva som faktisk skjer, men legger til Express for et realistisk applikasjonslag.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir https-tls-node &amp;&amp; cd https-tls-node\n$ npm init -y\n$ npm install express helmet\n$ mkdir certs src\n$ touch src\/server.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mappen <code>certs\/<\/code> holder n\u00f8kler og sertifikater. Legg den til i <code>.gitignore<\/code> umiddelbart. Private n\u00f8kler skal aldri inn i versjonskontroll, og en lekket n\u00f8kkel er en av de vanligste \u00e5rsakene til kompromitterte servere.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ echo \"certs\/\" &gt;&gt; .gitignore\n$ echo \"node_modules\/\" &gt;&gt; .gitignore<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-2-lag-et-selvsignert-sertifikat-med-openssl\">Steg 2: Lag et selvsignert sertifikat med OpenSSL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For lokal utvikling lager vi et selvsignert sertifikat med <strong>OpenSSL<\/strong>. Et selvsignert sertifikat er ikke klarert av nettlesere (du f\u00e5r en advarsel), men det krypterer forbindelsen helt likt et ekte sertifikat, og det er perfekt til testing. Kommandoen under lager en privat n\u00f8kkel og et sertifikat som er gyldig i 365 dager, med moderne elliptisk kurve (P-256) i stedet for treg RSA.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ openssl req -x509 -newkey ec \\\n  -pkeyopt ec_paramgen_curve:prime256v1 \\\n  -keyout certs\/key.pem \\\n  -out certs\/cert.pem \\\n  -days 365 -nodes \\\n  -subj \"\/C=NO\/ST=Oslo\/L=Oslo\/O=Dev\/CN=localhost\" \\\n  -addext \"subjectAltName=DNS:localhost,IP:127.0.0.1\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Flagget <code>-nodes<\/code> betyr \u00abno DES\u00bb, alts\u00e5 at den private n\u00f8kkelen ikke krypteres med passord. Det er greit lokalt, men i produksjon henter vi n\u00f8kkelen fra Let&#8217;s Encrypt i stedet. <code>subjectAltName<\/code> er kritisk: moderne nettlesere ignorerer feltet <code>CN<\/code> og krever at vertsnavnet st\u00e5r i SAN. Glemmer du dette, f\u00e5r du feilen <code>ERR_CERT_COMMON_NAME_INVALID<\/code>. Bekreft at sertifikatet ble laget riktig:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ openssl x509 -in certs\/cert.pem -noout -text | grep -A1 \"Subject Alternative Name\"\n            X509v3 Subject Alternative Name:\n                DNS:localhost, IP Address:127.0.0.1<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-3-din-forste-https-server-i-node-js\">Steg 3: Din f\u00f8rste HTTPS-server i Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e5 bygger vi den enkleste mulige <strong>HTTPS<\/strong>-serveren. Vi leser n\u00f8kkel og sertifikat fra disk og sender dem inn i <code>https.createServer()<\/code>. Legg merke til at vi bruker <code>fs.readFileSync<\/code> ved oppstart, ikke ved hver foresp\u00f8rsel: filene leses \u00e9n gang og holdes i minnet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/server.js\nimport https from 'node:https';\nimport fs from 'node:fs';\nimport express from 'express';\n\nconst app = express();\n\napp.get('\/', (req, res) =&gt; {\n  res.send('Hei fra en kryptert HTTPS-tilkobling.');\n});\n\nconst options = {\n  key: fs.readFileSync('certs\/key.pem'),\n  cert: fs.readFileSync('certs\/cert.pem'),\n};\n\nhttps.createServer(options, app).listen(8443, () =&gt; {\n  console.log('HTTPS-server kjorer pa https:\/\/localhost:8443');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sett <code>\"type\": \"module\"<\/code> i <code>package.json<\/code> for \u00e5 bruke <code>import<\/code>. Start serveren med <code>node src\/server.js<\/code> og \u00e5pne <code>https:\/\/localhost:8443<\/code>. Nettleseren advarer om sertifikatet (fordi det er selvsignert), men forbindelsen er kryptert. Tester du med curl, ser du resultatet av h\u00e5ndtrykket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -k -v https:\/\/localhost:8443 2&gt;&amp;1 | grep -i \"SSL connection\"\n* SSL connection using TLSv1.3 \/ TLS_AES_256_GCM_SHA384<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Flagget <code>-k<\/code> 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\u00e5ser vi dem fast slik at ingen klient kan presse forbindelsen ned til en svakere protokoll.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-4-tving-fram-tls-1-3-og-velg-chifferpakker\">Steg 4: Tving fram TLS 1.3 og velg chifferpakker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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\u00f8r du med <code>minVersion<\/code>. Vil du beholde TLS 1.2 for noen f\u00e5 gamle klienter, setter du <code>minVersion: 'TLSv1.2'<\/code> i stedet og definerer en streng chifferliste.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const options = {\n  key: fs.readFileSync('certs\/key.pem'),\n  cert: fs.readFileSync('certs\/cert.pem'),\n  \/\/ Krev TLS 1.3, avvis alt eldre\n  minVersion: 'TLSv1.3',\n  maxVersion: 'TLSv1.3',\n  \/\/ De tre godkjente TLS 1.3-pakkene\n  ciphers: [\n    'TLS_AES_256_GCM_SHA384',\n    'TLS_CHACHA20_POLY1305_SHA256',\n    'TLS_AES_128_GCM_SHA256',\n  ].join(':'),\n  honorCipherOrder: true,\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">De tre pakkene er de eneste TLS 1.3 definerer. <code>TLS_AES_256_GCM_SHA384<\/code> er f\u00f8rstevalget p\u00e5 maskiner med AES-akselerasjon (nesten alle moderne CPU-er). <code>TLS_CHACHA20_POLY1305_SHA256<\/code> er raskere p\u00e5 enheter uten slik akselerasjon, typisk eldre mobiler. <code>honorCipherOrder: true<\/code> betyr at serveren bestemmer rekkef\u00f8lgen, ikke klienten. Vil du st\u00f8tte begge TLS-versjoner samtidig, kan du la Mozilla generere en trygg konfigurasjon for deg via deres SSL Configuration Generator. Verifiser at serveren n\u00e5 avviser TLS 1.2:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -k --tlsv1.2 --tls-max 1.2 https:\/\/localhost:8443\ncurl: (35) error:0A00010B:SSL routines::wrong version number<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-5-omdiriger-all-http-trafikk-til-https\">Steg 5: Omdiriger all HTTP-trafikk til HTTPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En <strong>HTTPS<\/strong>-server alene hjelper lite hvis brukerne fortsatt kan n\u00e5 deg over vanlig HTTP. Du m\u00e5 lytte p\u00e5 port 80 og omdirigere alt til port 443 med statuskode 301 (permanent flytting). Dette er ogs\u00e5 det Let&#8217;s Encrypt bruker til \u00e5 validere domenet ditt, s\u00e5 porten m\u00e5 v\u00e6re \u00e5pen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import http from 'node:http';\n\n\/\/ Egen liten server kun for omdirigering\nhttp.createServer((req, res) =&gt; {\n  const host = req.headers.host?.split(':')[0] ?? 'localhost';\n  res.writeHead(301, {\n    Location: `https:\/\/${host}${req.url}`,\n  });\n  res.end();\n}).listen(80, () =&gt; {\n  console.log('HTTP-omdirigering aktiv pa port 80');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Bruk 301, ikke 302. En 302 (midlertidig) blir ikke bufret av nettlesere, s\u00e5 de pr\u00f8ver HTTP p\u00e5 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\u00f8rsel etter f\u00f8rste bes\u00f8k. Porter under 1024 krever rotrettigheter; i produksjon kj\u00f8rer du heller bak en reversproxy eller gir Node-prosessen <code>CAP_NET_BIND_SERVICE<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-6-hsts-og-sikkerhetshoder-med-helmet\">Steg 6: HSTS og sikkerhetshoder med Helmet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HSTS (HTTP Strict Transport Security) er et svar-hode som ber nettleseren om \u00e5 alltid bruke HTTPS for domenet ditt, selv om brukeren skriver <code>http:\/\/<\/code>. Det stenger en hel klasse angrep der en angriper avskj\u00e6rer den aller f\u00f8rste foresp\u00f8rselen. Vi bruker Helmet, som setter HSTS og en rekke andre sikkerhetshoder med fornuftige standardverdier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import helmet from 'helmet';\n\napp.use(helmet({\n  hsts: {\n    maxAge: 63072000,        \/\/ 2 ar i sekunder\n    includeSubDomains: true,\n    preload: true,\n  },\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      upgradeInsecureRequests: [],\n    },\n  },\n}));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verdien <code>maxAge: 63072000<\/code> er to \u00e5r, som er kravet for \u00e5 komme p\u00e5 HSTS-preloadlisten. Preloadlisten er bakt inn i nettleserne selv, slik at de bruker HTTPS for domenet ditt allerede f\u00f8r f\u00f8rste bes\u00f8k. N\u00e5r du er sikker p\u00e5 at hele domenet kj\u00f8rer HTTPS uten unntak, kan du sende det inn p\u00e5 hstspreload.org. V\u00e6r forsiktig: preload er vanskelig \u00e5 reversere raskt, s\u00e5 test grundig f\u00f8rst. Sjekk at hodet faktisk sendes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -kI https:\/\/localhost:8443 | grep -i strict\nstrict-transport-security: max-age=63072000; includeSubDomains; preload<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-7-ekte-sertifikat-med-lets-encrypt-og-certbot\">Steg 7: Ekte sertifikat med Let&#8217;s Encrypt og certbot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Selvsignerte sertifikater duger til testing, men i produksjon trenger du et sertifikat fra en klarert myndighet. Let&#8217;s Encrypt er den dominerende gratis sertifikatmyndigheten, med flere hundre millioner aktive sertifikater, og utsteder dem helautomatisk via ACME-protokollen. Verkt\u00f8yet certbot snakker ACME for deg. Installer det og hent et sertifikat for domenet ditt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo apt update &amp;&amp; sudo apt install certbot\n$ sudo certbot certonly --standalone \\\n  -d eksempel.no -d www.eksempel.no \\\n  --email admin@eksempel.no \\\n  --agree-tos --no-eff-email<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Modusen <code>--standalone<\/code> lar certbot starte sin egen lille webserver p\u00e5 port 80 for \u00e5 bevise at du kontrollerer domenet (HTTP-01-utfordringen). Derfor m\u00e5 du stoppe din egen server p\u00e5 port 80 mens dette kj\u00f8rer, eller bruke <code>--webroot<\/code> i stedet. N\u00e5r det er ferdig, ligger filene her:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem   # sertifikat + mellomledd\n\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem     # privat nokkel<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Bruk <code>fullchain.pem<\/code>, ikke <code>cert.pem<\/code>. Fullkjeden inneholder b\u00e5de ditt sertifikat og Let&#8217;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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const options = {\n  key: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem'),\n  cert: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem'),\n  minVersion: 'TLSv1.3',\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-8-automatisk-fornyelse-47-dagers-kravet\">Steg 8: Automatisk fornyelse (47-dagers kravet)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Med sertifikatlevetider p\u00e5 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\u00f8rrkj\u00f8ring:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo systemctl list-timers | grep certbot\n$ sudo certbot renew --dry-run<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Et problem gjenst\u00e5r: n\u00e5r 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 \u00e5 laste sertifikatet p\u00e5 nytt uten nedetid med <code>setSecureContext<\/code>. Her er deploy-hook-varianten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo certbot renew \\\n  --deploy-hook \"systemctl reload min-node-app\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For null nedetid kan du i stedet overv\u00e5ke sertifikatfilen og bytte ut sikkerhetskonteksten i farten. Da slipper klientene som er midt i en foresp\u00f8rsel \u00e5 bli kastet ut:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import tls from 'node:tls';\n\nconst server = https.createServer(options, app);\n\n\/\/ Bytt sertifikat uten omstart nar filen endres\nfs.watch('\/etc\/letsencrypt\/live\/eksempel.no\/', () =&gt; {\n  const ctx = tls.createSecureContext({\n    key: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem'),\n    cert: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem'),\n  });\n  server.setSecureContext(ctx);\n  console.log('Sertifikat lastet pa nytt uten nedetid');\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-9-sesjonsgjenopptak-og-ytelse\">Steg 9: Sesjonsgjenopptak og ytelse<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Et fullt TLS-h\u00e5ndtrykk koster CPU og en rundtur. Sesjonsgjenopptak lar en klient som har v\u00e6rt innom f\u00f8r hoppe over deler av h\u00e5ndtrykket. I TLS 1.3 skjer dette med \u00absession tickets\u00bb. Node.js h\u00e5ndterer dette automatisk, men i en klynge med flere prosesser m\u00e5 alle dele samme n\u00f8kkel for tickets, ellers kan ikke en klient gjenoppta en sesjon p\u00e5 en annen prosess.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import crypto from 'node:crypto';\n\n\/\/ Delt ticket-nokkel pa tvers av prosesser (48 byte)\nconst ticketKeys = crypto.randomBytes(48);\n\nconst options = {\n  key: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem'),\n  cert: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem'),\n  minVersion: 'TLSv1.3',\n  ticketKeys,                 \/\/ samme nokkel i alle arbeidsprosesser\n  sessionTimeout: 300,        \/\/ gjenopptak gyldig i 5 minutter\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pass p\u00e5 0-RTT. Det er fristende fordi det fjerner all h\u00e5ndtrykksforsinkelse, men 0-RTT-data er s\u00e5rbare for replay-angrep: en angriper kan spille av den samme foresp\u00f8rselen flere ganger. Bruk det aldri p\u00e5 foresp\u00f8rsler som endrer tilstand (POST, PUT, DELETE). Node.js har 0-RTT av som standard, og det b\u00f8r du la det v\u00e6re med mindre du vet n\u00f8yaktig hva du gj\u00f8r. 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\u00e5 automatisk fornyelse.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-10-post-kvante-tls-med-x25519mlkem768\">Steg 10: Post-kvante TLS med X25519MLKEM768<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En kvantedatamaskin som er kraftig nok vil kunne knekke dagens n\u00f8kkelutveksling og dekryptere trafikk angripere lagrer i dag (\u00abharvest now, decrypt later\u00bb). Som svar ruller Chrome og Cloudflare i 2025 ut en hybrid n\u00f8kkelutveksling kalt <code>X25519MLKEM768<\/code>, som kombinerer den klassiske X25519-kurven med den kvanteresistente ML-KEM-768 (tidligere Kyber). Knekker noen den ene, beskytter den andre fortsatt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Med en moderne OpenSSL 3.x-gren i Node.js 24 kan du be om denne gruppen via <code>ecdhCurve<\/code>. Dette er fortsatt nytt interoperabilitetsarbeid, s\u00e5 ikke gj\u00f8r det til et hardt krav som avviser eldre klienter enn\u00e5. Tilby det som et alternativ:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const options = {\n  key: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem'),\n  cert: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem'),\n  minVersion: 'TLSv1.3',\n  \/\/ Hybrid post-kvante forst, deretter klassisk fallback\n  ecdhCurve: 'X25519MLKEM768:X25519:prime256v1',\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Klienter som forst\u00e5r hybrid-gruppen bruker den; resten faller pent tilbake til X25519. Du f\u00e5r framtidssikring uten \u00e5 bryte noe. Vil du dykke dypere i hvorfor dette haster, har vi en bredere gjennomgang av <a href=\"\/no\/2026\/06\/10\/digitale-signaturer\/\">digitale signaturer og asymmetriske n\u00f8kler<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-11-komplett-fungerende-prosjekt\">Steg 11: Komplett, fungerende prosjekt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/server.js  (komplett)\nimport https from 'node:https';\nimport http from 'node:http';\nimport tls from 'node:tls';\nimport fs from 'node:fs';\nimport crypto from 'node:crypto';\nimport express from 'express';\nimport helmet from 'helmet';\n\nconst DOMAIN = process.env.DOMAIN ?? 'eksempel.no';\nconst CERT_DIR = `\/etc\/letsencrypt\/live\/${DOMAIN}`;\nconst app = express();\n\napp.use(helmet({\n  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },\n}));\n\napp.get('\/', (req, res) =&gt; {\n  res.send('Sikker HTTPS-tilkobling med TLS 1.3.');\n});\n\napp.get('\/health', (req, res) =&gt; res.json({ status: 'ok' }));\n\nfunction loadCerts() {\n  return {\n    key: fs.readFileSync(`${CERT_DIR}\/privkey.pem`),\n    cert: fs.readFileSync(`${CERT_DIR}\/fullchain.pem`),\n  };\n}\n\nconst options = {\n  ...loadCerts(),\n  minVersion: 'TLSv1.3',\n  ciphers: [\n    'TLS_AES_256_GCM_SHA384',\n    'TLS_CHACHA20_POLY1305_SHA256',\n    'TLS_AES_128_GCM_SHA256',\n  ].join(':'),\n  honorCipherOrder: true,\n  ecdhCurve: 'X25519MLKEM768:X25519:prime256v1',\n  ticketKeys: crypto.randomBytes(48),\n  sessionTimeout: 300,\n};\n\nconst server = https.createServer(options, app);\n\n\/\/ Varm omlasting nar certbot fornyer\nfs.watch(CERT_DIR, () =&gt; {\n  try {\n    server.setSecureContext(tls.createSecureContext(loadCerts()));\n    console.log('Sertifikat lastet pa nytt');\n  } catch (err) {\n    console.error('Klarte ikke laste sertifikat:', err.message);\n  }\n});\n\nserver.listen(443, () =&gt; console.log(`HTTPS aktiv pa https:\/\/${DOMAIN}`));\n\n\/\/ Omdiriger HTTP til HTTPS\nhttp.createServer((req, res) =&gt; {\n  const host = req.headers.host?.split(':')[0] ?? DOMAIN;\n  res.writeHead(301, { Location: `https:\/\/${host}${req.url}` });\n  res.end();\n}).listen(80);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Kj\u00f8r serveren bak en prosessoverv\u00e5ker som systemd eller PM2, slik at den starter p\u00e5 nytt ved krasj og ved omstart av serveren. Sett <code>DOMAIN<\/code> som milj\u00f8variabel, s\u00e5 kan samme kode kj\u00f8re i flere milj\u00f8er uten endring.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-12-verifiser-med-ssl-labs-og-curl\">Steg 12: Verifiser med SSL Labs og curl<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Test alltid den ferdige serveren utenfra. Det enkleste er Qualys SSL Labs-test, som gir karakter fra A+ til F og p\u00e5peker svakheter. M\u00e5l er A eller A+. Lokalt verifiserer du protokoll og chiffer med curl:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -vI https:\/\/eksempel.no 2&gt;&amp;1 | grep -iE \"SSL connection|HTTP\/\"\n* SSL connection using TLSv1.3 \/ TLS_AES_256_GCM_SHA384\nHTTP\/2 200\n\n$ echo | openssl s_client -connect eksempel.no:443 \\\n  -tls1_3 2&gt;\/dev\/null | grep -E \"Protocol|Cipher\"\n    Protocol  : TLSv1.3\n    Cipher    : TLS_AES_256_GCM_SHA384<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For en A+-karakter trenger du som regel TLS 1.3, HSTS med lang <code>maxAge<\/code>, ingen st\u00f8tte for TLS 1.0\/1.1, et gyldig sertifikat med full kjede og forward secrecy. F\u00f8lger du stegene over, har du alt dette. Kj\u00f8r testen p\u00e5 nytt etter hver st\u00f8rre endring i TLS-konfigurasjonen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"6-vanlige-fallgruver\">6 vanlige fallgruver<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Manglende subjectAltName.<\/strong> Nettlesere ignorerer <code>CN<\/code> og krever vertsnavnet i SAN. Uten det feiler sertifikatet med <code>ERR_CERT_COMMON_NAME_INVALID<\/code>, selv om alt annet er riktig.<\/li>\n<li><strong>Bruker cert.pem i stedet for fullchain.pem.<\/strong> Da mangler mellomsertifikatet. Det fungerer i Chrome, men feiler i Safari, Firefox og mange API-klienter som ikke bufrer mellomledd.<\/li>\n<li><strong>Glemmer \u00e5 forny.<\/strong> Med 47-dagers sertifikater p\u00e5 vei er manuell fornyelse en garantert nedetid. Sett opp certbots timer og test den med <code>--dry-run<\/code> f\u00f8r den trengs.<\/li>\n<li><strong>Private n\u00f8kler i Git.<\/strong> En lekket <code>privkey.pem<\/code> betyr at hele sikkerheten er borte. Legg <code>certs\/<\/code> i <code>.gitignore<\/code> fra f\u00f8rste commit, og roter n\u00f8kkelen umiddelbart hvis den noen gang er pushet.<\/li>\n<li><strong>0-RTT p\u00e5 skrivende endepunkter.<\/strong> Tidlig data kan spilles av p\u00e5 nytt. Aktiver aldri 0-RTT for POST, PUT eller DELETE. La det st\u00e5 av med mindre du har en idempotent leseoperasjon.<\/li>\n<li><strong>Tillater fortsatt TLS 1.0\/1.1.<\/strong> Disse er foreldet og senker SSL Labs-karakteren din. Sett <code>minVersion<\/code> til minst <code>TLSv1.2<\/code>, helst <code>TLSv1.3<\/code>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"feilsoking-8-vanlige-problemer\">Feils\u00f8king: 8 vanlige problemer<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Feilmelding \/ symptom<\/th><th>Sannsynlig \u00e5rsak<\/th><th>L\u00f8sning<\/th><\/tr><\/thead><tbody><tr><td><code>EACCES: permission denied :443<\/code><\/td><td>Porter under 1024 krever rot<\/td><td>Bruk <code>setcap<\/code> eller reversproxy<\/td><\/tr><tr><td><code>ERR_CERT_COMMON_NAME_INVALID<\/code><\/td><td>Vertsnavn mangler i SAN<\/td><td>Legg til <code>subjectAltName<\/code><\/td><\/tr><tr><td><code>ERR_CERT_AUTHORITY_INVALID<\/code><\/td><td>Selvsignert eller ufullstendig kjede<\/td><td>Bruk <code>fullchain.pem<\/code><\/td><\/tr><tr><td><code>wrong version number<\/code><\/td><td>Klient pr\u00f8ver feil TLS-versjon<\/td><td>Sjekk <code>minVersion<\/code>\/<code>maxVersion<\/code><\/td><\/tr><tr><td><code>ERR_SSL_PROTOCOL_ERROR<\/code><\/td><td>HTTP sendt til HTTPS-port<\/td><td>Bruk <code>https:\/\/<\/code>, ikke <code>http:\/\/<\/code><\/td><\/tr><tr><td>Curl: <code>(60) SSL certificate problem<\/code><\/td><td>Klient stoler ikke p\u00e5 CA<\/td><td><code>-k<\/code> lokalt, ekte sertifikat i prod<\/td><\/tr><tr><td>NET::ERR_CERT_DATE_INVALID<\/td><td>Sertifikat utl\u00f8pt<\/td><td>Forny med <code>certbot renew<\/code><\/td><\/tr><tr><td>Fornyelse feiler p\u00e5 port 80<\/td><td>Port 80 opptatt eller blokkert<\/td><td>Stopp tjenesten eller bruk <code>--webroot<\/code><\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-block-table__caption\">De vanligste TLS-feilene og hvordan du retter dem.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">De fleste TLS-problemer faller i \u00e9n av tre kategorier: feil med sertifikatkjeden, feil med protokollversjon eller feil med rettigheter p\u00e5 lave porter. N\u00e5r noe svikter, start med <code>openssl s_client -connect domene:443<\/code>. Den viser hele kjeden, protokollen og chifferet, og avsl\u00f8rer som regel \u00e5rsaken p\u00e5 sekunder.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avanserte-tips-for-produksjon\">Avanserte tips for produksjon<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kjor-bak-en-reversproxy\">Kj\u00f8r bak en reversproxy<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I de fleste produksjonsoppsett terminerer du TLS i nginx, Caddy eller en lastbalanserer, ikke i Node.js direkte. Det gir deg sentralisert sertifikath\u00e5ndtering, enklere lastbalansering og bedre beskyttelse mot tilkoblingsangrep. Caddy henter og fornyer til og med Let&#8217;s Encrypt-sertifikater helautomatisk. Da kj\u00f8rer Node-appen din ren HTTP p\u00e5 en intern port, og proxyen st\u00e5r for HTTPS utad. Husk \u00e5 sette <code>app.set('trust proxy', 1)<\/code> slik at Express stoler p\u00e5 <code>X-Forwarded-Proto<\/code>-hodet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"overvak-utlop-for-det-smerter\">Overv\u00e5k utl\u00f8p f\u00f8r det smerter<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Med korte levetider er et utl\u00f8pt sertifikat den mest sannsynlige nedetids\u00e5rsaken. Sett opp overv\u00e5king som varsler deg minst sju dager f\u00f8r utl\u00f8p. En enkel cron-jobb med <code>openssl x509 -enddate -noout<\/code> som sammenligner mot dagens dato holder, men dedikerte tjenester som overv\u00e5ker sertifikatutl\u00f8p er enda tryggere. Logg ogs\u00e5 hver vellykkede fornyelse, slik at du vet at automatikken faktisk kj\u00f8rer.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"test-med-ssl-labs-i-ci\">Test med SSL Labs i CI<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Du kan kj\u00f8re 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\u00f8r den n\u00e5r produksjon. Kombiner dette med en automatisk sjekk av at HSTS-hodet og omdirigeringen fra HTTP fortsatt fungerer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"forsta-sertifikatkjeden-og-tillitsankeret\">Forst\u00e5 sertifikatkjeden og tillitsankeret<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Et <strong>SSL-sertifikat<\/strong> (riktigere: TLS-sertifikat) er ikke en isolert fil. Det er en lenke i en kjede som ender i et rotsertifikat nettleseren stoler p\u00e5 fra f\u00f8r. N\u00e5r en klient kobler til <strong>HTTPS<\/strong>-serveren din, f\u00e5r den serverens sertifikat pluss ett eller flere mellomsertifikater. Klienten f\u00f8lger 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\u00e5r brukeren en advarsel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dette forklarer hvorfor <code>fullchain.pem<\/code> er s\u00e5 viktig. Rotsertifikatet ligger allerede hos klienten, men mellomsertifikatet gj\u00f8r 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\u00f8r, men Safari, eldre Android og mange programmatiske klienter feiler hardt. Du kan inspisere hele kjeden serveren faktisk sender med OpenSSL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ echo | openssl s_client -connect eksempel.no:443 \\\n  -showcerts 2&gt;\/dev\/null | grep -E \"s:|i:\"\n 0 s:CN=eksempel.no\n   i:C=US, O=Let's Encrypt, CN=R13\n 1 s:C=US, O=Let's Encrypt, CN=R13\n   i:C=US, O=Internet Security Research Group, CN=ISRG Root X1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Her ser du to ledd: serverens eget sertifikat (niv\u00e5 0), signert av Let&#8217;s Encrypts mellomsertifikat R13, som igjen er signert av roten ISRG Root X1. Roten er selvsignert og ligger i tillitslageret. Forst\u00e5r du denne strukturen, blir de fleste sertifikatfeil enkle \u00e5 diagnostisere: nesten alltid mangler et ledd, er utl\u00f8pt, eller har feil vertsnavn. Vil du grave dypere i hvordan signaturer og tillit henger sammen matematisk, anbefaler vi gjennomgangen v\u00e5r av <a href=\"\/no\/2026\/06\/10\/sha-256\/\">SHA-256 og digitale avtrykk<\/a>, som er byggeklossen under hver eneste sertifikatsignatur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"http-2-og-http-3-over-tls\">HTTP\/2 og HTTP\/3 over TLS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">TLS handler ikke bare om sikkerhet, men ogs\u00e5 om hvilken protokoll som kj\u00f8rer opp\u00e5. Moderne nettlesere foretrekker HTTP\/2 og HTTP\/3, som multiplekser mange foresp\u00f8rsler over \u00e9n forbindelse og fjerner \u00abhead-of-line blocking\u00bb fra HTTP\/1.1. Hvilken versjon som velges, forhandles under TLS-h\u00e5ndtrykket via en utvidelse som heter ALPN (Application-Layer Protocol Negotiation). Det fine er at HTTP\/2 alltid krever TLS i praksis, s\u00e5 n\u00e5r du f\u00f8rst har <strong>HTTPS<\/strong> p\u00e5 plass, er du nesten klar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js har en egen <code>http2<\/code>-modul som snakker HTTP\/2 med samme sertifikater. Du bytter ut <code>https.createServer<\/code> med <code>http2.createSecureServer<\/code> og setter <code>allowHTTP1: true<\/code> for \u00e5 falle tilbake til HTTP\/1.1 for klienter som ikke st\u00f8tter HTTP\/2:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import http2 from 'node:http2';\nimport fs from 'node:fs';\n\nconst server = http2.createSecureServer({\n  key: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem'),\n  cert: fs.readFileSync('\/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem'),\n  minVersion: 'TLSv1.3',\n  allowHTTP1: true,   \/\/ fall tilbake til HTTP\/1.1 ved behov\n});\n\nserver.on('stream', (stream) =&gt; {\n  stream.respond({ ':status': 200, 'content-type': 'text\/plain' });\n  stream.end('Levert over HTTP\/2 og TLS 1.3');\n});\n\nserver.listen(443);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">HTTP\/3 g\u00e5r et skritt lenger og bytter ut TCP med QUIC, som kj\u00f8rer over UDP og har TLS 1.3 bakt direkte inn i transportlaget. Det fjerner den ekstra rundturen TCP-h\u00e5ndtrykket koster, slik at den totale oppkoblingstiden synker ytterligere. Node.js har eksperimentell QUIC-st\u00f8tte, 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\u00e5 konfigurasjonen vi har bygget opp gjelder fullt ut.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tls-1-3-bak-en-nginx-reversproxy\">TLS 1.3 bak en nginx-reversproxy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">De fleste norske og nordiske produksjonsoppsett kj\u00f8rer Node.js bak nginx. Da terminerer nginx <strong>TLS<\/strong>, og Node-appen lytter p\u00e5 en intern HTTP-port. Fordelen er sentralisert sertifikath\u00e5ndtering 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\u00e5 port 3000:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 443 ssl;\n    http2 on;\n    server_name eksempel.no;\n\n    ssl_certificate     \/etc\/letsencrypt\/live\/eksempel.no\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/eksempel.no\/privkey.pem;\n\n    ssl_protocols TLSv1.3;\n    ssl_prefer_server_ciphers off;\n\n    add_header Strict-Transport-Security\n        \"max-age=63072000; includeSubDomains; preload\" always;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto https;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n\nserver {\n    listen 80;\n    server_name eksempel.no;\n    return 301 https:\/\/$host$request_uri;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Legg merke til <code>X-Forwarded-Proto https<\/code>. Uten dette hodet vet ikke Express at den opprinnelige foresp\u00f8rselen kom over <strong>HTTPS<\/strong>, og logikk som tvinger sikre cookies eller omdirigerer kan g\u00e5 i loop. Sett derfor <code>app.set('trust proxy', 1)<\/code> i Node-appen. Med <code>ssl_protocols TLSv1.3<\/code> avviser nginx alt eldre p\u00e5 samme m\u00e5te som <code>minVersion<\/code> gjorde i Node. Test konfigurasjonen med <code>nginx -t<\/code> f\u00f8r du laster den p\u00e5 nytt, og verifiser resultatet med en SSL Labs-kj\u00f8ring. Et oppsett som dette gir typisk karakteren A+ uten ekstra arbeid, s\u00e5 lenge sertifikatkjeden er komplett og fornyelsen er automatisert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"gjensidig-tls-mtls-for-tjeneste-til-tjeneste\">Gjensidig TLS (mTLS) for tjeneste-til-tjeneste<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I vanlig <strong>HTTPS<\/strong> autentiserer bare serveren seg. Klienten sjekker serverens sertifikat, men serveren vet ikke hvem klienten er f\u00f8r et eventuelt innloggingssteg p\u00e5 applikasjonsniv\u00e5. For intern trafikk mellom tjenester, API-er med h\u00f8ye 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 <strong>TLS<\/strong>-laget f\u00f8r en eneste byte applikasjonsdata sendes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I Node.js sl\u00e5r du p\u00e5 mTLS med to alternativer: <code>requestCert: true<\/code> ber klienten om et sertifikat, og <code>rejectUnauthorized: true<\/code> avviser klienter som ikke kan vise et gyldig sertifikat signert av en CA du stoler p\u00e5. Du oppgir den klarerte CA-en med <code>ca<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const options = {\n  key: fs.readFileSync('certs\/server-key.pem'),\n  cert: fs.readFileSync('certs\/server-cert.pem'),\n  \/\/ Krev og verifiser klientsertifikat\n  requestCert: true,\n  rejectUnauthorized: true,\n  ca: [fs.readFileSync('certs\/intern-ca.pem')],\n  minVersion: 'TLSv1.3',\n};\n\nhttps.createServer(options, (req, res) =&gt; {\n  const cert = req.socket.getPeerCertificate();\n  res.end(`Hei, klient: ${cert.subject.CN}`);\n}).listen(8443);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Med dette p\u00e5 plass kan ingen koble til uten et gyldig klientsertifikat. Du henter klientens identitet fra <code>req.socket.getPeerCertificate()<\/code> og kan bruke <code>CN<\/code> eller andre felt til autorisasjon. mTLS er ryggraden i mange tjenestenett (service mesh) som Istio og Linkerd, der hver tjeneste f\u00e5r sitt eget kortlivede sertifikat og all intern trafikk er kryptert og autentisert. For et selvbygd oppsett trenger du en intern CA til \u00e5 signere b\u00e5de server- og klientsertifikater, noe du kan sette opp med OpenSSL eller verkt\u00f8y som cfssl.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ofte-stilte-sporsmal\">Ofte stilte sp\u00f8rsm\u00e5l<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"trenger-jeg-https-pa-en-intern-tjeneste\">Trenger jeg HTTPS p\u00e5 en intern tjeneste?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja. Interne nettverk er ikke trygge. Angripere som f\u00f8rst 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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hva-er-forskjellen-pa-ssl-og-tls\">Hva er forskjellen p\u00e5 SSL og TLS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SSL er forgjengeren til TLS og er fullstendig foreldet; selv SSL 3.0 er usikker. N\u00e5r folk sier \u00abSSL-sertifikat\u00bb, mener de egentlig et TLS-sertifikat. Protokollen du faktisk bruker i 2026 er TLS 1.3, eventuelt TLS 1.2 for gamle klienter. Begrepet \u00abSSL\u00bb henger igjen av historiske grunner.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"er-lets-encrypt-trygt-nok-for-produksjon\">Er Let&#8217;s Encrypt trygt nok for produksjon?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja. Et Let&#8217;s Encrypt-sertifikat gir n\u00f8yaktig 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\u00e6re riktig, men for de fleste tjenester er Let&#8217;s Encrypt det opplagte valget.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"bor-jeg-sla-pa-hsts-preload-med-en-gang\">B\u00f8r jeg sl\u00e5 p\u00e5 HSTS preload med en gang?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nei, vent. Preload er bakt inn i nettleserne og er treg \u00e5 reversere. Bekreft f\u00f8rst at hele domenet, inkludert alle underdomener, fungerer feilfritt over HTTPS i flere uker. Send inn til preloadlisten f\u00f8rst n\u00e5r du er helt sikker, ellers risikerer du at deler av siden blir utilgjengelig.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hva-er-x25519mlkem768-og-ma-jeg-bruke-det-na\">Hva er X25519MLKEM768, og m\u00e5 jeg bruke det n\u00e5?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Det er en hybrid n\u00f8kkelutveksling som kombinerer klassisk X25519 med den kvanteresistente ML-KEM-768. Chrome og Cloudflare rullet det ut i 2025 for \u00e5 beskytte mot framtidige kvantedatamaskiner. Du m\u00e5 ikke kreve det enn\u00e5, men \u00e5 tilby det som alternativ koster ingenting og gir framtidssikring. Les mer om temaet i v\u00e5r oversikt over <a href=\"\/no\/2026\/06\/10\/cryptography-hub\/\">kryptografi og digital tillit<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hvorfor-faller-ssl-labs-karakteren-min-til-b\">Hvorfor faller SSL Labs-karakteren min til B?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">De vanligste \u00e5rsakene er st\u00f8tte for TLS 1.0\/1.1, svake chiffer, manglende forward secrecy eller en ufullstendig sertifikatkjede. Sett <code>minVersion<\/code> til <code>TLSv1.2<\/code> eller h\u00f8yere, bruk bare moderne chiffer og send <code>fullchain.pem<\/code>. Kj\u00f8r testen p\u00e5 nytt, s\u00e5 ser du som regel karakteren stige til A.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kan-jeg-bruke-samme-oppsett-med-deno-eller-bun\">Kan jeg bruke samme oppsett med Deno eller Bun?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Prinsippene er identiske, men API-et er litt annerledes. B\u00e5de Deno og Bun har innebygd TLS-st\u00f8tte og kan lese de samme PEM-filene fra Let&#8217;s Encrypt. Begrepene minVersion, chifferpakker og HSTS gjelder likt. Denne veiledningen fokuserer p\u00e5 Node.js fordi det fortsatt er det mest utbredte i produksjon.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"relatert-innhold\">Relatert innhold<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/no\/2026\/06\/10\/https-og-tls\/\">HTTPS og TLS: slik beskyttes forbindelsen din p\u00e5 nett<\/a><\/li>\n<li><a href=\"\/no\/2026\/06\/14\/scrypt-passordhashing-nodejs\/\">scrypt passordhashing i Node.js: 11 steg<\/a><\/li>\n<li><a href=\"\/no\/2026\/06\/13\/ssh-nokkel-linux\/\">SSH-n\u00f8kkel i Linux: sikker innlogging i 10 steg<\/a><\/li>\n<li><a href=\"\/no\/2026\/06\/12\/wireguard-vpn-linux\/\">WireGuard VPN p\u00e5 Linux: 12 steg, 1472 Mbps<\/a><\/li>\n<li><a href=\"\/no\/2026\/06\/10\/digitale-signaturer\/\">Digitale signaturer: hashfunksjoner og asymmetriske n\u00f8kler<\/a><\/li>\n<li><a href=\"\/no\/2026\/06\/10\/security-hub\/\">Nettsikkerhet: datalekkasjer, passord, HTTPS og phishing<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"eksterne-kilder\">Eksterne kilder<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/nodejs.org\/api\/tls.html\" target=\"_blank\" rel=\"noopener\">Node.js: offisiell dokumentasjon for tls-modulen<\/a><\/li>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8446\" target=\"_blank\" rel=\"noopener\">RFC 8446: TLS 1.3-spesifikasjonen<\/a><\/li>\n<li><a href=\"https:\/\/ssl-config.mozilla.org\/\" target=\"_blank\" rel=\"noopener\">Mozilla SSL Configuration Generator<\/a><\/li>\n<li><a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\" rel=\"noopener\">Let&#8217;s Encrypt: gratis automatiserte sertifikater<\/a><\/li>\n<li><a href=\"https:\/\/www.ssllabs.com\/ssltest\/\" target=\"_blank\" rel=\"noopener\">Qualys SSL Labs Server Test<\/a><\/li>\n<li><a href=\"https:\/\/hstspreload.org\/\" target=\"_blank\" rel=\"noopener\">HSTS Preload-liste<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>HTTPS er ikke lenger valgfritt. Over 95 prosent av all sidelasting i Chrome skjer i dag over kryptert tilkobling, if\u00f8lge Googles Transparency Report, og nettlesere markerer rene HTTP-sider som \u00abikke\u2026<\/p>\n","protected":false},"author":6,"featured_media":89,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-88","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/88","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/comments?post=88"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/88\/revisions"}],"predecessor-version":[{"id":90,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/88\/revisions\/90"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/media\/89"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/media?parent=88"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/categories?post=88"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/tags?post=88"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}