How to Create an HTTPS Server with Netty
A step-by-step Netty HTTPS server example: generate a certificate, add an SslHandler to the pipeline, serve a response and verify the TLS connection.
To make a Netty server speak HTTPS, build an SslContext from a certificate and private key, then put the handler it creates first in each connection's channel pipeline, ahead of the HTTP codec. Everything after that point works like a plain HTTP server, because TLS encryption and decryption happen before your HTTP handlers see any data. The steps below build a minimal working example, then show how to check that TLS is active.
One clarification changes how you read the rest: this guide covers Netty 4.1-style code. Netty 4.2 deprecates NioEventLoopGroup in favor of a MultiThreadIoEventLoopGroup created with an I/O handler factory. The TLS setup is the same in both, but adjust the event loop lines if your project uses 4.2.
Why HTTPS matters for camera and microphone pages
If you are serving a page that tests a webcam or microphone, HTTPS is not optional. Browsers only expose camera and microphone capture APIs such as getUserMedia in a secure context, meaning a page loaded over HTTPS or from the local machine itself (localhost). Open the same page over plain HTTP from another device on your network and the browser will typically refuse camera access before any permission prompt appears.
Keep three things separate. HTTPS only makes the page eligible to ask for the camera. The visitor must still grant permission, and even then a granted permission does not guarantee that video frames are actually captured, or that call quality is good. Those are later checks.
Step 1: Add the Netty dependency
The example uses the all-in-one artifact for simplicity. Use a current release from the line your project targets; no specific version is assumed here.
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version><!-- current 4.1.x release --></version>
</dependency>
Step 2: Get a certificate and private key
A TLS server needs a certificate (the public part clients see) and the matching private key (kept secret on the server). For local testing, a self-signed certificate is enough. This OpenSSL command creates both in PEM format, with the certificate issued for localhost:
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout key.pem -out cert.pem \
-days 365 -subj "/CN=localhost"
Netty's file-based SslContextBuilder expects the private key in PKCS#8 format, which begins with the line BEGIN PRIVATE KEY. If your key file instead says BEGIN RSA PRIVATE KEY, convert it:
openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pem
For production, use a certificate issued by a public certificate authority for your real hostname. Browsers do not trust self-signed certificates and will show a warning that the visitor has to bypass manually.
Step 3: Write the server
The key line is sslCtx.newHandler(ch.alloc()), added first in the pipeline. HttpServerCodec parses HTTP, HttpObjectAggregator combines a request into a single object, and the last handler writes the response.
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.File;
public final class HttpsServer {
public static void main(String[] args) throws Exception {
int port = 8443;
SslContext sslCtx = SslContextBuilder
.forServer(new File("cert.pem"), new File("key.pem"))
.build();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(sslCtx.newHandler(ch.alloc()));
p.addLast(new HttpServerCodec());
p.addLast(new HttpObjectAggregator(65536));
p.addLast(new HelloHandler());
}
});
Channel ch = b.bind(port).sync().channel();
System.out.println("HTTPS server listening on port " + port);
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
final class HelloHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello over HTTPS\n", CharsetUtil.UTF_8));
res.headers()
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8")
.setInt(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
if (HttpUtil.isKeepAlive(req)) {
ctx.writeAndFlush(res);
} else {
ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
If you skipped Step 2 and only want a throwaway test, Netty also ships SelfSignedCertificate in io.netty.handler.ssl.util, which generates a temporary certificate at startup. Replace the forServer call with forServer(ssc.certificate(), ssc.privateKey()) after creating new SelfSignedCertificate(). It is meant for testing only, and on some JDKs it needs Bouncy Castle on the classpath.
Step 4: Verify that TLS is working
Start the server, then confirm the TLS handshake from a terminal. OpenSSL's test client shows the certificate the server presents and the negotiated protocol:
openssl s_client -connect localhost:8443
- A successful handshake prints the certificate chain, including the subject CN=localhost, and a line naming the TLS protocol version.
- With curl, request the same host and port using the https scheme and add -k so curl accepts the self-signed certificate; the body should read Hello over HTTPS.
- In a browser, open the server's https address on port 8443. Expect a certificate warning with a self-signed certificate; after you proceed, the same text should appear.
- If you request the server over plain http instead, the connection fails or returns garbage, which confirms the port only accepts TLS.
Common problems
| Symptom | Likely cause | Fix |
| IllegalArgumentException about the key file at startup | Private key is not in PKCS#8 format | Convert it with openssl pkcs8 -topk8 -nocrypt |
| Browser shows a certificate warning | Self-signed certificate, or the name does not match the hostname used | Accept it for local tests; use a CA-issued certificate for the real hostname in production |
| Handshake errors in the logs, no HTTP requests reach the handler | SslHandler is not first in the pipeline, or the client is speaking plain HTTP | Add the SSL handler before HttpServerCodec and connect with https |
| Camera page loads but the browser never asks for permission | Page is not in a secure context, or permission was previously blocked for that site | Serve over HTTPS or localhost, then check the site's camera permission in browser settings |
Limitations and next steps
This example serves HTTP/1.1 over TLS with the JDK's built-in TLS implementation. It does not cover HTTP/2 negotiation, client certificate authentication, automatic certificate renewal or the optional native OpenSSL provider; each needs extra configuration. No benchmark or performance data was supplied for this guide, so no claims are made about throughput or which TLS provider is faster. Before deploying, replace the test certificate, store the private key with restricted file permissions, and keep the Netty version up to date for security fixes.