[{"data":1,"prerenderedAt":1335},["ShallowReactive",2],{"/knowledge-base/mtls":3,"/knowledge-base/mtls-surround":1326},{"id":4,"title":5,"body":6,"category":1307,"contentType":1308,"date":1309,"description":1310,"extension":1311,"head":1312,"image":1313,"meta":1314,"navigation":328,"ogImage":1312,"path":1315,"readingTime":1316,"robots":1312,"schemaOrg":1312,"seo":1317,"sitemap":1318,"stem":1319,"tags":1320,"__hash__":1325},"knowledgeBase/knowledge-base/mtls.md","mTLS CookBook With Caddy",{"type":7,"value":8,"toc":1289},"minimark",[9,13,18,21,127,131,134,250,263,267,270,275,282,490,494,497,544,554,558,561,565,568,666,670,673,838,842,849,938,951,955,958,962,965,1066,1070,1073,1154,1158,1161,1221,1224,1285],[10,11,12],"p",{},"Mutual TLS (mTLS) provides robust authentication by requiring both the server and client to present valid certificates. This tutorial guides you through setting up mTLS with Caddy, creating a private certificate authority (CA), and configuring client certificates.",[14,15,17],"h2",{"id":16},"overview","Overview",[10,19,20],{},"This setup uses four distinct nodes to demonstrate a complete infrastructure. The examples use internal IP addresses (LAN and WireGuard) to restrict access.",[22,23,24,43],"table",{},[25,26,27],"thead",{},[28,29,30,34,37,40],"tr",{},[31,32,33],"th",{},"Node",[31,35,36],{},"Purpose",[31,38,39],{},"LAN IP",[31,41,42],{},"WireGuard IP",[44,45,46,67,87,107],"tbody",{},[28,47,48,55,58,64],{},[49,50,51],"td",{},[52,53,54],"strong",{},"Database VM",[49,56,57],{},"MySQL, Grafana, and Loki",[49,59,60],{},[61,62,63],"code",{},"10.10.10.1",[49,65,66],{},"None",[28,68,69,74,77,82],{},[49,70,71],{},[52,72,73],{},"API VM",[49,75,76],{},"Backend server (e.g., Strapi) running Caddy with mTLS",[49,78,79],{},[61,80,81],{},"10.10.10.2",[49,83,84],{},[61,85,86],{},"10.100.102.109",[28,88,89,94,97,102],{},[49,90,91],{},[52,92,93],{},"BFF VM",[49,95,96],{},"Public-facing Caddy proxy and Cloudflare tunnel",[49,98,99],{},[61,100,101],{},"10.10.10.3",[49,103,104],{},[61,105,106],{},"10.100.102.103",[28,108,109,114,117,122],{},[49,110,111],{},[52,112,113],{},"Workstation",[49,115,116],{},"Local machine or browser client",[49,118,119],{},[61,120,121],{},"10.10.10.10",[49,123,124],{},[61,125,126],{},"10.100.102.102",[14,128,130],{"id":129},"pki-requirements","PKI Requirements",[10,132,133],{},"A successful mTLS deployment requires several cryptographic files. Keep private keys strictly confidential on their respective machines.",[22,135,136,151],{},[25,137,138],{},[28,139,140,143,146,148],{},[31,141,142],{},"File",[31,144,145],{},"Location",[31,147,36],{},[31,149,150],{},"Secret",[44,152,153,169,191,212,232],{},[28,154,155,160,163,166],{},[49,156,157],{},[61,158,159],{},"root.crt",[49,161,162],{},"API VM, BFF VM, Workstation",[49,164,165],{},"Public certificate of your private CA",[49,167,168],{},"No",[28,170,171,176,183,186],{},[49,172,173],{},[61,174,175],{},"root.key",[49,177,178,179,182],{},"API VM ONLY (",[61,180,181],{},"/var/lib/caddy/.../root.key",")",[49,184,185],{},"CA private key used to sign client certificates",[49,187,188],{},[52,189,190],{},"Yes",[28,192,193,202,204,207],{},[49,194,195,198,199],{},[61,196,197],{},"server.key"," & ",[61,200,201],{},"server.crt",[49,203,73],{},[49,205,206],{},"Identifies the server to connecting clients",[49,208,209,211],{},[52,210,190],{}," (Key)",[28,213,214,220,227,230],{},[49,215,216,217,182],{},"Client Certificates (",[61,218,219],{},"*.crt",[49,221,222,223,226],{},"API VM (",[61,224,225],{},"/etc/caddy/client-leaves/",") and Clients",[49,228,229],{},"Whitelisted leaf certificates",[49,231,168],{},[28,233,234,240,243,246],{},[49,235,236,237,182],{},"Client Keys (",[61,238,239],{},"*.key",[49,241,242],{},"BFF VM, Workstation",[49,244,245],{},"Signs the client side of the handshake",[49,247,248],{},[52,249,190],{},[251,252,253],"warning",{},[10,254,255,256,258,259,262],{},"Never distribute ",[61,257,175],{}," or any ",[61,260,261],{},".key"," file across the network. Private keys must remain on the device where they are generated.",[14,264,266],{"id":265},"caddy-configuration","Caddy Configuration",[10,268,269],{},"Configure Caddy on the backend to enforce mTLS verification, and configure the frontend proxy to provide the required client certificate.",[271,272,274],"h3",{"id":273},"backend-server-api-vm","Backend Server (API VM)",[10,276,277,278,281],{},"Add the following configuration to the API VM's ",[61,279,280],{},"Caddyfile"," to enforce client certificate verification and restrict access to specific IP addresses.",[283,284,289],"pre",{"className":285,"code":286,"filename":280,"language":287,"meta":288,"style":288},"language-caddyfile shiki shiki-themes github-light github-dark github-dark","{\n    # Whitelist specific proxies that forward traffic\n    trusted_proxies 10.10.10.3 10.10.10.2\n    trusted_proxies_strict true\n}\n\nhttps://content.com {\n    tls internal\n\n    tls {\n        client_auth {\n            mode require_and_verify\n            \n            # Trust the root CA and specific whitelisted client certificates\n            trusted_ca_cert_file          /etc/caddy/client-leaves/root.crt\n            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi.crt\n            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi-ws.crt\n        }\n    }\n\n    encode gzip\n\n    @allowedIPs {\n        remote_ip 10.10.10.3 10.10.10.10\n    }\n    \n    handle @allowedIPs {\n        reverse_proxy 127.0.0.1:1337\n    }\n\n    handle {\n        respond \"Access Denied\" 403\n    }\n}\n","caddyfile","",[61,290,291,299,305,311,317,323,330,336,342,347,353,359,365,371,377,383,389,395,401,407,412,418,423,429,435,440,446,452,458,463,468,474,480,485],{"__ignoreMap":288},[292,293,296],"span",{"class":294,"line":295},"line",1,[292,297,298],{},"{\n",[292,300,302],{"class":294,"line":301},2,[292,303,304],{},"    # Whitelist specific proxies that forward traffic\n",[292,306,308],{"class":294,"line":307},3,[292,309,310],{},"    trusted_proxies 10.10.10.3 10.10.10.2\n",[292,312,314],{"class":294,"line":313},4,[292,315,316],{},"    trusted_proxies_strict true\n",[292,318,320],{"class":294,"line":319},5,[292,321,322],{},"}\n",[292,324,326],{"class":294,"line":325},6,[292,327,329],{"emptyLinePlaceholder":328},true,"\n",[292,331,333],{"class":294,"line":332},7,[292,334,335],{},"https://content.com {\n",[292,337,339],{"class":294,"line":338},8,[292,340,341],{},"    tls internal\n",[292,343,345],{"class":294,"line":344},9,[292,346,329],{"emptyLinePlaceholder":328},[292,348,350],{"class":294,"line":349},10,[292,351,352],{},"    tls {\n",[292,354,356],{"class":294,"line":355},11,[292,357,358],{},"        client_auth {\n",[292,360,362],{"class":294,"line":361},12,[292,363,364],{},"            mode require_and_verify\n",[292,366,368],{"class":294,"line":367},13,[292,369,370],{},"            \n",[292,372,374],{"class":294,"line":373},14,[292,375,376],{},"            # Trust the root CA and specific whitelisted client certificates\n",[292,378,380],{"class":294,"line":379},15,[292,381,382],{},"            trusted_ca_cert_file          /etc/caddy/client-leaves/root.crt\n",[292,384,386],{"class":294,"line":385},16,[292,387,388],{},"            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi.crt\n",[292,390,392],{"class":294,"line":391},17,[292,393,394],{},"            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi-ws.crt\n",[292,396,398],{"class":294,"line":397},18,[292,399,400],{},"        }\n",[292,402,404],{"class":294,"line":403},19,[292,405,406],{},"    }\n",[292,408,410],{"class":294,"line":409},20,[292,411,329],{"emptyLinePlaceholder":328},[292,413,415],{"class":294,"line":414},21,[292,416,417],{},"    encode gzip\n",[292,419,421],{"class":294,"line":420},22,[292,422,329],{"emptyLinePlaceholder":328},[292,424,426],{"class":294,"line":425},23,[292,427,428],{},"    @allowedIPs {\n",[292,430,432],{"class":294,"line":431},24,[292,433,434],{},"        remote_ip 10.10.10.3 10.10.10.10\n",[292,436,438],{"class":294,"line":437},25,[292,439,406],{},[292,441,443],{"class":294,"line":442},26,[292,444,445],{},"    \n",[292,447,449],{"class":294,"line":448},27,[292,450,451],{},"    handle @allowedIPs {\n",[292,453,455],{"class":294,"line":454},28,[292,456,457],{},"        reverse_proxy 127.0.0.1:1337\n",[292,459,461],{"class":294,"line":460},29,[292,462,406],{},[292,464,466],{"class":294,"line":465},30,[292,467,329],{"emptyLinePlaceholder":328},[292,469,471],{"class":294,"line":470},31,[292,472,473],{},"    handle {\n",[292,475,477],{"class":294,"line":476},32,[292,478,479],{},"        respond \"Access Denied\" 403\n",[292,481,483],{"class":294,"line":482},33,[292,484,406],{},[292,486,488],{"class":294,"line":487},34,[292,489,322],{},[271,491,493],{"id":492},"frontend-proxy-bff-vm","Frontend Proxy (BFF VM)",[10,495,496],{},"Configure the proxy to present a client certificate when forwarding requests to the secure backend.",[283,498,500],{"className":285,"code":499,"filename":280,"language":287,"meta":288,"style":288},"http://127.0.0.1:8080 {\n    reverse_proxy https://10.10.10.2:443 {\n        transport http {\n            tls_server_name content.com\n            tls_trusted_ca_certs /var/lib/caddy/mtls/root.crt\n            tls_client_auth      /var/lib/caddy/mtls/strapi.crt /var/lib/caddy/mtls/strapi.key\n        }\n    }\n}\n",[61,501,502,507,512,517,522,527,532,536,540],{"__ignoreMap":288},[292,503,504],{"class":294,"line":295},[292,505,506],{},"http://127.0.0.1:8080 {\n",[292,508,509],{"class":294,"line":301},[292,510,511],{},"    reverse_proxy https://10.10.10.2:443 {\n",[292,513,514],{"class":294,"line":307},[292,515,516],{},"        transport http {\n",[292,518,519],{"class":294,"line":313},[292,520,521],{},"            tls_server_name content.com\n",[292,523,524],{"class":294,"line":319},[292,525,526],{},"            tls_trusted_ca_certs /var/lib/caddy/mtls/root.crt\n",[292,528,529],{"class":294,"line":325},[292,530,531],{},"            tls_client_auth      /var/lib/caddy/mtls/strapi.crt /var/lib/caddy/mtls/strapi.key\n",[292,533,534],{"class":294,"line":332},[292,535,400],{},[292,537,538],{"class":294,"line":338},[292,539,406],{},[292,541,542],{"class":294,"line":344},[292,543,322],{},[545,546,547],"note",{},[10,548,549,550,553],{},"If using a tunnel like Cloudflared, direct it to ",[61,551,552],{},"http://127.0.0.1:8080",".",[14,555,557],{"id":556},"client-certificate-operations","Client Certificate Operations",[10,559,560],{},"Generate a new client certificate for each workstation or service that needs access to the API.",[271,562,564],{"id":563},"_1-create-a-key-and-csr-workstation","1. Create a Key and CSR (Workstation)",[10,566,567],{},"Generate a private key and a Certificate Signing Request (CSR) on the client machine.",[283,569,574],{"className":570,"code":571,"filename":572,"language":573,"meta":288,"style":288},"language-bash shiki shiki-themes github-light github-dark github-dark","# Generate a 4096-bit RSA private key\nopenssl genpkey -algorithm RSA -out ~/client.key -pkeyopt rsa_keygen_bits:4096\n\n# Create a certificate signing request\nopenssl req -new -key ~/client.key -subj \"/CN=workstation-client\" -out ~/client.csr\n\n# Transfer the CSR to the API VM for signing\nscp ~/client.csr user@api-vm:/tmp/\n","Terminal","bash",[61,575,576,582,611,615,620,646,650,655],{"__ignoreMap":288},[292,577,578],{"class":294,"line":295},[292,579,581],{"class":580},"sCsY4","# Generate a 4096-bit RSA private key\n",[292,583,584,588,592,596,599,602,605,608],{"class":294,"line":301},[292,585,587],{"class":586},"shcOC","openssl",[292,589,591],{"class":590},"sfrk1"," genpkey",[292,593,595],{"class":594},"suiK_"," -algorithm",[292,597,598],{"class":590}," RSA",[292,600,601],{"class":594}," -out",[292,603,604],{"class":590}," ~/client.key",[292,606,607],{"class":594}," -pkeyopt",[292,609,610],{"class":590}," rsa_keygen_bits:4096\n",[292,612,613],{"class":294,"line":307},[292,614,329],{"emptyLinePlaceholder":328},[292,616,617],{"class":294,"line":313},[292,618,619],{"class":580},"# Create a certificate signing request\n",[292,621,622,624,627,630,633,635,638,641,643],{"class":294,"line":319},[292,623,587],{"class":586},[292,625,626],{"class":590}," req",[292,628,629],{"class":594}," -new",[292,631,632],{"class":594}," -key",[292,634,604],{"class":590},[292,636,637],{"class":594}," -subj",[292,639,640],{"class":590}," \"/CN=workstation-client\"",[292,642,601],{"class":594},[292,644,645],{"class":590}," ~/client.csr\n",[292,647,648],{"class":294,"line":325},[292,649,329],{"emptyLinePlaceholder":328},[292,651,652],{"class":294,"line":332},[292,653,654],{"class":580},"# Transfer the CSR to the API VM for signing\n",[292,656,657,660,663],{"class":294,"line":338},[292,658,659],{"class":586},"scp",[292,661,662],{"class":590}," ~/client.csr",[292,664,665],{"class":590}," user@api-vm:/tmp/\n",[271,667,669],{"id":668},"_2-sign-the-certificate-api-vm","2. Sign the Certificate (API VM)",[10,671,672],{},"Use the Caddy internal CA to sign the client's CSR. This creates a valid client certificate.",[283,674,676],{"className":570,"code":675,"filename":572,"language":573,"meta":288,"style":288},"CA_DIR=/var/lib/caddy/.local/share/caddy/pki/authorities/local\nCSR=/tmp/client.csr\nCRT=/etc/caddy/client-leaves/client.crt\n\n# Sign the CSR to issue a 1-year certificate\nsudo openssl x509 -req -in $CSR -CA $CA_DIR/root.crt -CAkey $CA_DIR/root.key \\\n  -CAcreateserial -out $CRT -days 365 -sha256\n\n# Fix ownership and permissions\nsudo chown caddy:caddy $CRT\nsudo chmod 644 $CRT\n\n# Reload Caddy to recognize the new certificate\nsudo caddy reload --config /etc/caddy/Caddyfile\n",[61,677,678,691,701,711,715,720,760,779,783,788,801,813,817,822],{"__ignoreMap":288},[292,679,680,684,688],{"class":294,"line":295},[292,681,683],{"class":682},"slsVL","CA_DIR",[292,685,687],{"class":686},"so5gQ","=",[292,689,690],{"class":590},"/var/lib/caddy/.local/share/caddy/pki/authorities/local\n",[292,692,693,696,698],{"class":294,"line":301},[292,694,695],{"class":682},"CSR",[292,697,687],{"class":686},[292,699,700],{"class":590},"/tmp/client.csr\n",[292,702,703,706,708],{"class":294,"line":307},[292,704,705],{"class":682},"CRT",[292,707,687],{"class":686},[292,709,710],{"class":590},"/etc/caddy/client-leaves/client.crt\n",[292,712,713],{"class":294,"line":313},[292,714,329],{"emptyLinePlaceholder":328},[292,716,717],{"class":294,"line":319},[292,718,719],{"class":580},"# Sign the CSR to issue a 1-year certificate\n",[292,721,722,725,728,731,734,737,740,743,746,749,752,754,757],{"class":294,"line":325},[292,723,724],{"class":586},"sudo",[292,726,727],{"class":590}," openssl",[292,729,730],{"class":590}," x509",[292,732,733],{"class":594}," -req",[292,735,736],{"class":594}," -in",[292,738,739],{"class":682}," $CSR ",[292,741,742],{"class":594},"-CA",[292,744,745],{"class":682}," $CA_DIR",[292,747,748],{"class":590},"/root.crt",[292,750,751],{"class":594}," -CAkey",[292,753,745],{"class":682},[292,755,756],{"class":590},"/root.key",[292,758,759],{"class":594}," \\\n",[292,761,762,765,767,770,773,776],{"class":294,"line":332},[292,763,764],{"class":594},"  -CAcreateserial",[292,766,601],{"class":594},[292,768,769],{"class":682}," $CRT ",[292,771,772],{"class":594},"-days",[292,774,775],{"class":594}," 365",[292,777,778],{"class":594}," -sha256\n",[292,780,781],{"class":294,"line":338},[292,782,329],{"emptyLinePlaceholder":328},[292,784,785],{"class":294,"line":344},[292,786,787],{"class":580},"# Fix ownership and permissions\n",[292,789,790,792,795,798],{"class":294,"line":349},[292,791,724],{"class":586},[292,793,794],{"class":590}," chown",[292,796,797],{"class":590}," caddy:caddy",[292,799,800],{"class":682}," $CRT\n",[292,802,803,805,808,811],{"class":294,"line":355},[292,804,724],{"class":586},[292,806,807],{"class":590}," chmod",[292,809,810],{"class":594}," 644",[292,812,800],{"class":682},[292,814,815],{"class":294,"line":361},[292,816,329],{"emptyLinePlaceholder":328},[292,818,819],{"class":294,"line":367},[292,820,821],{"class":580},"# Reload Caddy to recognize the new certificate\n",[292,823,824,826,829,832,835],{"class":294,"line":373},[292,825,724],{"class":586},[292,827,828],{"class":590}," caddy",[292,830,831],{"class":590}," reload",[292,833,834],{"class":594}," --config",[292,836,837],{"class":590}," /etc/caddy/Caddyfile\n",[271,839,841],{"id":840},"_3-import-the-certificate-workstation","3. Import the Certificate (Workstation)",[10,843,844,845,848],{},"Transfer the signed certificate back to the workstation and bundle it into a PKCS#12 (",[61,846,847],{},".p12",") file for browser installation.",[283,850,852],{"className":570,"code":851,"filename":572,"language":573,"meta":288,"style":288},"# Fetch the signed certificate from the API VM\nscp user@api-vm:/etc/caddy/client-leaves/client.crt ~/\n\n# Bundle the key and certificate (you will be prompted to set an export password)\nopenssl pkcs12 -export -inkey ~/client.key -in ~/client.crt -certfile ~/root.crt -name \"Secure-Client\" -out ~/client.p12\n\n# Clean up the raw files after importing the bundle into your browser\nshred -u ~/client.key ~/client.crt ~/client.p12\n",[61,853,854,859,869,873,878,915,919,924],{"__ignoreMap":288},[292,855,856],{"class":294,"line":295},[292,857,858],{"class":580},"# Fetch the signed certificate from the API VM\n",[292,860,861,863,866],{"class":294,"line":301},[292,862,659],{"class":586},[292,864,865],{"class":590}," user@api-vm:/etc/caddy/client-leaves/client.crt",[292,867,868],{"class":590}," ~/\n",[292,870,871],{"class":294,"line":307},[292,872,329],{"emptyLinePlaceholder":328},[292,874,875],{"class":294,"line":313},[292,876,877],{"class":580},"# Bundle the key and certificate (you will be prompted to set an export password)\n",[292,879,880,882,885,888,891,893,895,898,901,904,907,910,912],{"class":294,"line":319},[292,881,587],{"class":586},[292,883,884],{"class":590}," pkcs12",[292,886,887],{"class":594}," -export",[292,889,890],{"class":594}," -inkey",[292,892,604],{"class":590},[292,894,736],{"class":594},[292,896,897],{"class":590}," ~/client.crt",[292,899,900],{"class":594}," -certfile",[292,902,903],{"class":590}," ~/root.crt",[292,905,906],{"class":594}," -name",[292,908,909],{"class":590}," \"Secure-Client\"",[292,911,601],{"class":594},[292,913,914],{"class":590}," ~/client.p12\n",[292,916,917],{"class":294,"line":325},[292,918,329],{"emptyLinePlaceholder":328},[292,920,921],{"class":294,"line":332},[292,922,923],{"class":580},"# Clean up the raw files after importing the bundle into your browser\n",[292,925,926,929,932,934,936],{"class":294,"line":338},[292,927,928],{"class":586},"shred",[292,930,931],{"class":594}," -u",[292,933,604],{"class":590},[292,935,897],{"class":590},[292,937,914],{"class":590},[939,940,941],"tip",{},[10,942,943,944,947,948,950],{},"To revoke access, simply delete the client's ",[61,945,946],{},".crt"," file from ",[61,949,225],{}," on the API VM and reload Caddy.",[14,952,954],{"id":953},"network-security","Network Security",[10,956,957],{},"Restrict network traffic using UFW and WireGuard to ensure clients can only connect via authorized paths.",[271,959,961],{"id":960},"ufw-rules","UFW Rules",[10,963,964],{},"Configure the firewall on each node to drop unexpected traffic.",[283,966,968],{"className":570,"code":967,"filename":572,"language":573,"meta":288,"style":288},"# Allow MySQL access strictly from the API VM\nsudo ufw allow in on wg0 proto tcp from 10.10.10.2 to any port 9000 comment 'MySQL from API'\n\n# Allow incoming HTTPS traffic on the API VM from the proxy\nsudo ufw allow in proto tcp from 10.10.10.3 to any port 443 comment 'HTTPS from BFF'\n",[61,969,970,975,1024,1028,1033],{"__ignoreMap":288},[292,971,972],{"class":294,"line":295},[292,973,974],{"class":580},"# Allow MySQL access strictly from the API VM\n",[292,976,977,979,982,985,988,991,994,997,1000,1003,1006,1009,1012,1015,1018,1021],{"class":294,"line":301},[292,978,724],{"class":586},[292,980,981],{"class":590}," ufw",[292,983,984],{"class":590}," allow",[292,986,987],{"class":590}," in",[292,989,990],{"class":590}," on",[292,992,993],{"class":590}," wg0",[292,995,996],{"class":590}," proto",[292,998,999],{"class":590}," tcp",[292,1001,1002],{"class":590}," from",[292,1004,1005],{"class":594}," 10.10.10.2",[292,1007,1008],{"class":590}," to",[292,1010,1011],{"class":590}," any",[292,1013,1014],{"class":590}," port",[292,1016,1017],{"class":594}," 9000",[292,1019,1020],{"class":590}," comment",[292,1022,1023],{"class":590}," 'MySQL from API'\n",[292,1025,1026],{"class":294,"line":307},[292,1027,329],{"emptyLinePlaceholder":328},[292,1029,1030],{"class":294,"line":313},[292,1031,1032],{"class":580},"# Allow incoming HTTPS traffic on the API VM from the proxy\n",[292,1034,1035,1037,1039,1041,1043,1045,1047,1049,1052,1054,1056,1058,1061,1063],{"class":294,"line":319},[292,1036,724],{"class":586},[292,1038,981],{"class":590},[292,1040,984],{"class":590},[292,1042,987],{"class":590},[292,1044,996],{"class":590},[292,1046,999],{"class":590},[292,1048,1002],{"class":590},[292,1050,1051],{"class":594}," 10.10.10.3",[292,1053,1008],{"class":590},[292,1055,1011],{"class":590},[292,1057,1014],{"class":590},[292,1059,1060],{"class":594}," 443",[292,1062,1020],{"class":590},[292,1064,1065],{"class":590}," 'HTTPS from BFF'\n",[271,1067,1069],{"id":1068},"express-application-setup","Express Application Setup",[10,1071,1072],{},"If you run an Express backend behind Caddy, configure the application to trust the proxies forwarding the traffic.",[283,1074,1079],{"className":1075,"code":1076,"filename":1077,"language":1078,"meta":288,"style":288},"language-typescript shiki shiki-themes github-light github-dark github-dark","// Trust the local loopback and the BFF VM proxy\napp.set('trust proxy', (ip: string) => {\n  return ip === '127.0.0.1' || ip === '10.10.10.3';\n});\n","app.ts","typescript",[61,1080,1081,1086,1122,1149],{"__ignoreMap":288},[292,1082,1083],{"class":294,"line":295},[292,1084,1085],{"class":580},"// Trust the local loopback and the BFF VM proxy\n",[292,1087,1088,1091,1094,1097,1100,1103,1107,1110,1113,1116,1119],{"class":294,"line":301},[292,1089,1090],{"class":682},"app.",[292,1092,1093],{"class":586},"set",[292,1095,1096],{"class":682},"(",[292,1098,1099],{"class":590},"'trust proxy'",[292,1101,1102],{"class":682},", (",[292,1104,1106],{"class":1105},"sQHwn","ip",[292,1108,1109],{"class":686},":",[292,1111,1112],{"class":594}," string",[292,1114,1115],{"class":682},") ",[292,1117,1118],{"class":686},"=>",[292,1120,1121],{"class":682}," {\n",[292,1123,1124,1127,1130,1133,1136,1139,1141,1143,1146],{"class":294,"line":307},[292,1125,1126],{"class":686},"  return",[292,1128,1129],{"class":682}," ip ",[292,1131,1132],{"class":686},"===",[292,1134,1135],{"class":590}," '127.0.0.1'",[292,1137,1138],{"class":686}," ||",[292,1140,1129],{"class":682},[292,1142,1132],{"class":686},[292,1144,1145],{"class":590}," '10.10.10.3'",[292,1147,1148],{"class":682},";\n",[292,1150,1151],{"class":294,"line":313},[292,1152,1153],{"class":682},"});\n",[14,1155,1157],{"id":1156},"toolbox-and-verification","Toolbox and Verification",[10,1159,1160],{},"Use these commands to troubleshoot and verify your mTLS configuration.",[22,1162,1163,1173],{},[25,1164,1165],{},[28,1166,1167,1170],{},[31,1168,1169],{},"Task",[31,1171,1172],{},"Command",[44,1174,1175,1191,1201,1211],{},[28,1176,1177,1180],{},[49,1178,1179],{},"Verify key and certificate match",[49,1181,1182,1185,1188],{},[61,1183,1184],{},"openssl rsa -in key.pem -noout -modulus | openssl md5",[1186,1187],"br",{},[61,1189,1190],{},"openssl x509 -in cert.pem -noout -modulus | openssl md5",[28,1192,1193,1196],{},[49,1194,1195],{},"Test connection manually",[49,1197,1198],{},[61,1199,1200],{},"openssl s_client -connect content.com:443 -cert client.crt -key client.key -CAfile root.crt",[28,1202,1203,1206],{},[49,1204,1205],{},"Validate Caddyfile syntax",[49,1207,1208],{},[61,1209,1210],{},"sudo caddy validate --config /etc/caddy/Caddyfile",[28,1212,1213,1216],{},[49,1214,1215],{},"Format Caddyfile",[49,1217,1218],{},[61,1219,1220],{},"sudo caddy fmt --overwrite /etc/caddy/Caddyfile",[10,1222,1223],{},"To quickly test authentication from the command line:",[283,1225,1227],{"className":570,"code":1226,"filename":572,"language":573,"meta":288,"style":288},"# Should succeed and return HTTP 200\ncurl -sv https://content.com/admin --cert ~/client.crt --key ~/client.key --cacert ~/root.crt -o /dev/null\n\n# Should fail and return an OpenSSL alert (certificate required)\ncurl -k https://content.com/admin\n",[61,1228,1229,1234,1266,1270,1275],{"__ignoreMap":288},[292,1230,1231],{"class":294,"line":295},[292,1232,1233],{"class":580},"# Should succeed and return HTTP 200\n",[292,1235,1236,1239,1242,1245,1248,1250,1253,1255,1258,1260,1263],{"class":294,"line":301},[292,1237,1238],{"class":586},"curl",[292,1240,1241],{"class":594}," -sv",[292,1243,1244],{"class":590}," https://content.com/admin",[292,1246,1247],{"class":594}," --cert",[292,1249,897],{"class":590},[292,1251,1252],{"class":594}," --key",[292,1254,604],{"class":590},[292,1256,1257],{"class":594}," --cacert",[292,1259,903],{"class":590},[292,1261,1262],{"class":594}," -o",[292,1264,1265],{"class":590}," /dev/null\n",[292,1267,1268],{"class":294,"line":307},[292,1269,329],{"emptyLinePlaceholder":328},[292,1271,1272],{"class":294,"line":313},[292,1273,1274],{"class":580},"# Should fail and return an OpenSSL alert (certificate required)\n",[292,1276,1277,1279,1282],{"class":294,"line":319},[292,1278,1238],{"class":586},[292,1280,1281],{"class":594}," -k",[292,1283,1284],{"class":590}," https://content.com/admin\n",[1286,1287,1288],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":288,"searchDepth":301,"depth":301,"links":1290},[1291,1292,1293,1297,1302,1306],{"id":16,"depth":301,"text":17},{"id":129,"depth":301,"text":130},{"id":265,"depth":301,"text":266,"children":1294},[1295,1296],{"id":273,"depth":307,"text":274},{"id":492,"depth":307,"text":493},{"id":556,"depth":301,"text":557,"children":1298},[1299,1300,1301],{"id":563,"depth":307,"text":564},{"id":668,"depth":307,"text":669},{"id":840,"depth":307,"text":841},{"id":953,"depth":301,"text":954,"children":1303},[1304,1305],{"id":960,"depth":307,"text":961},{"id":1068,"depth":307,"text":1069},{"id":1156,"depth":301,"text":1157},"networking","tutorial","2026-07-15","A comprehensive cookbook for configuring Mutual TLS (mTLS) authentication using the Caddy web server.","md",null,"/knowledge-base/mtls.jpeg",{},"/knowledge-base/mtls","10",{"title":5,"description":1310},{"loc":1315},"knowledge-base/mtls",[1321,1322,1323,1324],"mTLS","Caddy","Security","Networking","XGsX7vWhgA6LfeASuS0DhXjPUgCluKg2MB8spCONa6A",[1327,1331],{"title":1328,"path":1329,"stem":1330,"children":-1},"JWT Implementation: Refresh Tokens vs. Private Key","/knowledge-base/jwt","knowledge-base/jwt",{"title":1332,"path":1333,"stem":1334,"children":-1},"SSH CookBook","/knowledge-base/ssh-recipe","knowledge-base/ssh-recipe",1784306722468]