How to Run an HTTPS Server in Node.js for Camera and Microphone Testing
Create an HTTPS server with Node.js's built-in https module, generate a local certificate, and confirm that the browser treats your page as a secure context for camera access.
The short answer
Node.js includes a built-in https module. To run an HTTPS server, you need a private key and a certificate. Load both files with the fs module, pass them to https.createServer, and call listen on a port. For local camera or microphone testing, a self-signed or locally trusted certificate is enough. A public website needs a certificate from a recognized certificate authority.
Check whether you need HTTPS before you set it up. Browsers already treat localhost as a secure context. If you open your test page only on the computer that runs Node.js and use localhost as the address, plain HTTP is usually enough. You need HTTPS when you open the page from another device by IP address or hostname, such as a phone on the same Wi-Fi network checking its camera.
Why camera pages need a secure context
A secure context is a page that the browser considers safely delivered, which in practice means HTTPS or localhost. Browsers make the camera and microphone API, navigator.mediaDevices.getUserMedia, available only in secure contexts. On a plain HTTP page served from an address other than localhost, navigator.mediaDevices is typically undefined. The permission prompt never appears, and the page can't request the camera at all.
A secure context is a requirement for camera access, but it does not guarantee that the camera works. Getting a usable picture involves three separate stages. First, the browser exposes the API and you grant permission. Second, the operating system lets the browser use the device, and the camera actually delivers video frames. Third, the image and sound are good enough for a call. HTTPS only affects the first stage.
Step 1: Create a key and certificate
The quickest option is a self-signed certificate made with OpenSSL. The command below creates a private key (key.pem) and a certificate (cert.pem) for localhost. The subjectAltName entry matters because current browsers check that field rather than the common name. If you plan to connect from another device, add that machine's local IP address to the list, for example IP:192.168.1.50 with your own address.
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout key.pem -out cert.pem -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Browsers show a warning for self-signed certificates because no trusted authority vouches for them. To avoid the warning on your own computer, you can use a tool such as mkcert. It installs a local certificate authority on your machine and issues certificates that your browsers trust. Other devices, such as a phone, will still show the warning unless you install that local authority on them too.
mkcert -install
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1
Treat key.pem as a secret. Keep it out of version control and out of any folder the server shares publicly.
Step 2: Write the HTTPS server
Save the following as server.js in the same folder as the key and certificate. The server returns a small test page that checks for a secure context and then tries to start the camera. Paths are built from __dirname, so the files load correctly no matter which folder you start Node.js from.
const https = require('node:https');
const fs = require('node:fs');
const path = require('node:path');
const options = {
key: fs.readFileSync(path.join(__dirname, 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'cert.pem')),
};
const page = `<!doctype html>
<meta charset="utf-8">
<video id="v" autoplay playsinline muted></video>
<p id="s"></p>
<script>
const s = document.getElementById('s');
if (!window.isSecureContext) {
s.textContent = 'Not a secure context';
} else {
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
document.getElementById('v').srcObject = stream;
s.textContent = 'Camera stream started';
})
.catch(err => { s.textContent = 'Camera error: ' + err.name; });
}
</script>`;
https.createServer(options, (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(page);
}).listen(8443, () => {
console.log('HTTPS server listening on port 8443');
});
If your project uses ES modules, replace the require lines with import statements such as import https from 'node:https'. Then build file paths from import.meta.dirname, or from import.meta.url on older Node.js versions. If you already use a framework such as Express, pass your app as the request handler: https.createServer(options, app).listen(8443).
Step 3: Run the server and check the result
- 1. Start the server with: node server.js
- 2. Open the browser and enter an address with the https scheme, the host localhost, and port 8443. From another device, use the computer's local IP address as the host instead.
- 3. If you see a certificate warning, the browser does not trust the certificate. For local testing with a self-signed certificate, you can proceed past it. For a cleaner setup, use a locally trusted certificate.
- 4. Read the status line on the page. 'Not a secure context' means the page did not load as a secure page. Any other message means the camera API was available.
- 5. When the browser asks, allow camera access, then confirm that the video element shows a live image.
You can also check from the browser's developer console. Typing window.isSecureContext should return true. Typing typeof navigator.mediaDevices should return 'object' rather than 'undefined'.
What the page result means
| Result | Meaning | Next step |
| Certificate warning page | The browser doesn't trust the certificate | Proceed for local testing, or use a locally trusted certificate |
| Not a secure context | The page loaded over plain HTTP from an address other than localhost | Make sure the address uses the https scheme and the HTTPS port |
| Camera error: NotAllowedError | Access was denied by you, by the site setting, or by the operating system's privacy settings | Reset the site's camera permission and check the operating system's camera privacy setting for the browser |
| Camera error: NotFoundError | The browser can't find any camera | Check the connection, the drivers, and whether the camera appears in the system's device list |
| Camera error: NotReadableError | A camera was found but couldn't be opened | Close other apps that may be using the camera, then try again |
| Camera stream started, but the picture is black or frozen | Access worked, but the camera isn't delivering a usable image | Check the privacy shutter or lens cover, then test the camera in another app |
Common server errors
- EADDRINUSE: another process is already using the port. Stop that process or choose a different port number.
- EACCES when listening: on Linux and macOS, ports below 1024 (including the standard HTTPS port 443) normally require elevated privileges. For testing, use a higher port such as 8443.
- ENOENT on startup: Node.js can't find key.pem or cert.pem. Check the file names and their location.
- The browser reports an SSL protocol error: you probably opened the HTTPS port using the http scheme, or the reverse. Match the scheme to the server.
- Other devices can't connect: confirm that the devices are on the same network, that you used the computer's correct local IP address, and that a firewall isn't blocking the port. Also confirm that the certificate includes that IP address or hostname.
Limitations to keep in mind
Self-signed and locally trusted certificates are meant for development and private testing. For a site that other people will visit, use a certificate from a recognized certificate authority. Another common approach is to place Node.js behind a reverse proxy that handles HTTPS for it.
This guide is based on general behavior of Node.js and current browsers. No browser-specific or version-specific test results were provided. Browsers can differ in how they label certificate warnings and how they display permission prompts. Behavior may also differ between versions of Node.js and of each browser, so check the documentation for your versions if something doesn't match what is described here.