Using Mutual TLS (mTLS) in Next.js (Server-Side Only)
Using Mutual TLS (mTLS) in Next.js (Server-Side Only)
In the previous posts, we covered:
Now we focus on Next.js applications and how mTLS works depending on deployment.
Next.js Cannot Access TLS Handshake Directly
- Next.js middleware and API routes run after the TLS handshake
- They cannot see client certificates or verify them
- Next.js built-in server does not expose Node's HTTPS options like
requestCert
In short: Next.js middleware cannot enforce mTLS or access TLS handshake details. Any enforcement must happen before the request reaches Next.js.
Next.js as an mTLS Client (Server-Side API Calls)
Next.js can securely call mTLS-protected APIs from server-side code, such as:
- API routes
- Server actions
import fs from 'fs';
import https from 'https';
import axios from 'axios';
export async function GET(req) {
const httpsAgent = new https.Agent({
key: fs.readFileSync('/certs/client.key'),
cert: fs.readFileSync('/certs/client.crt'),
ca: fs.readFileSync('/certs/ca.pem'),
rejectUnauthorized: true,
});
try {
const response = await axios.get('https://internal-api.example.com', {
httpsAgent,
});
return new Response(JSON.stringify(response.data), { status: 200 });
} catch (err) {
console.error(err);
return new Response('mTLS request failed', { status: 500 });
}
}
This works both on Vercel and self-hosted deployments because it happens server-side.
Next.js as an mTLS Server
Next.js cannot enforce mTLS directly. How you handle mTLS depends on your deployment:
1. Vercel Deployment
- You cannot run a custom HTTPS server on Vercel.
- Next.js middleware and API routes cannot access TLS handshake or client certs.
- Solution: Terminate mTLS at a reverse proxy or API gateway in front of Vercel.
- The proxy validates client certificates, then forwards requests to Vercel.
- Inject client info into headers (e.g.,
X-Client-CN) so Next.js can read it.
Example Nginx snippet:
server {
listen 443 ssl;
ssl_certificate /etc/certs/server.crt;
ssl_certificate_key /etc/certs/server.key;
ssl_client_certificate /etc/certs/ca.pem;
ssl_verify_client on;
location / {
proxy_pass https://my-vercel-app.vercel.app;
proxy_set_header X-Client-CN $ssl_client_s_dn;
}
}
Next.js API route reading the header:
export async function GET(req) {
const clientCN = req.headers.get('x-client-cn');
return new Response(`Hello ${clientCN}`, { status: 200 });
}
---
2. Self-Hosted Deployment (Docker, VPS, Kubernetes, OpenShift)
- You can run a custom Node HTTPS server wrapping Next.js.
- Use
requestCert: trueandrejectUnauthorized: trueto enforce client certs. - Forward requests to
app.getRequestHandler().
Example:
import https from 'https';
import fs from 'fs';
import next from 'next';
const app = next({ dev: true });
const handle = app.getRequestHandler();
const server = https.createServer(
{
key: fs.readFileSync('/certs/server.key'),
cert: fs.readFileSync('/certs/server.crt'),
ca: fs.readFileSync('/certs/ca.pem'),
requestCert: true,
rejectUnauthorized: true,
},
(req, res) => handle(req, res)
);
server.listen(8443, () => console.log('Next.js mTLS server running on port 8443'));
This is only possible for **self-hosted deployments**. Platforms like Vercel do not allow custom HTTPS servers.
Summary: Vercel vs Self-Hosted
- Vercel: mTLS must be terminated upstream (reverse proxy/API gateway). Next.js reads client info from headers.
- Self-hosted: You can wrap Next.js in a custom HTTPS server and enforce client certificates directly.
- Next.js middleware cannot enforce mTLS or access TLS handshake details in any deployment.
- Server-side outgoing requests (mTLS client) work in both environments.
Using these patterns ensures your Next.js app participates in enterprise mTLS workflows securely, without exposing sensitive certificates.
❤️ Support This Blog
If this post helped you, you can support my writing with a small donation. Thank you for reading.
Comments
Post a Comment