Jetty Connectors and Protocols
Connectors are the network components through which Jetty accepts incoming network connections.
Each connector listens on a network port and can be configured with ConnectionFactory
components that understand one or more network protocols.
Understanding a protocol means that the connector is able to interpret incoming network bytes (for example, the bytes that represent an HTTP/1.1 request) and convert them into more abstract objects (for example an HttpServletRequest
object) that are then processed by applications.
Conversely, an abstract object (for example an HttpServletResponse
) is converted into the correspondent outgoing network bytes (the bytes that represent an HTTP/1.1 response).
Like other Jetty components, connectors are enabled and configured by enabling and configuring the correspondent Jetty module.
Recall that you must always issue the commands to enable Jetty modules from within the $JETTY_BASE directory, and that the Jetty module configuration files are in the $JETTY_BASE/start.d/ directory.
|
You can obtain the list of connector-related modules in this way:
$ java -jar $JETTY_HOME/start.jar --list-modules=connector
Clear-Text HTTP/1.1
Clear text HTTP/1.1 is enabled with the http
Jetty module with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http
2024-10-01 18:01:01.055:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:01.111:INFO :oejs.AbstractConnector:main: Started ServerConnector@6a2b953e{HTTP/1.1, (http/1.1)}{0.0.0.0:8080} 2024-10-01 18:01:01.133:INFO :oejs.Server:main: Started oejs.Server@6150c3ec{STARTING}[12.0.15-SNAPSHOT,sto=5000] @2000ms
After having enabled the http
module, the $JETTY_BASE
directory looks like this:
JETTY_BASE
├── resources
│ └── jetty-logging.properties
└── start.d
└── http.ini
The http.ini
file is the file that you want to edit to configure network and protocol parameters — for more details see this section.
Note that the http
Jetty module depends on the server
Jetty module.
Some parameters that you may want to configure are in fact common HTTP parameters that are applied not only for clear-text HTTP/1.1, but also for secure HTTP/1.1 or for clear-text HTTP/2 or for encrypted HTTP/2, or for HTTP/3, and these configuration parameters may be present in the server
module configuration file.
You can force the creation of the server.ini
file via:
$ java -jar $JETTY_HOME/start.jar --add-modules=server
Now the $JETTY_BASE
directory looks like this:
JETTY_BASE
├── resources
│ └── jetty-logging.properties
└── start.d
├── http.ini
└── server.ini
Now you can edit the server.ini
file — for more details see this section.
Secure HTTP/1.1
Secure HTTP/1.1 is enabled with both the ssl
and https
Jetty modules with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,https
INFO : mkdir ${jetty.base}/start.d INFO : server transitively enabled, ini template available with --add-modules=server INFO : logging-jetty transitively enabled INFO : resources transitively enabled INFO : https initialized in ${jetty.base}/start.d/https.ini INFO : ssl initialized in ${jetty.base}/start.d/ssl.ini INFO : threadpool transitively enabled, ini template available with --add-modules=threadpool INFO : logging/slf4j transitive provider of logging/slf4j for logging-jetty INFO : logging/slf4j dynamic dependency of logging-jetty INFO : bytebufferpool transitively enabled, ini template available with --add-modules=bytebufferpool INFO : mkdir ${jetty.base}/resources INFO : copy ${jetty.home}/modules/logging/jetty/resources/jetty-logging.properties to ${jetty.base}/resources/jetty-logging.properties INFO : Base directory was modified
The command above enables the ssl
module, that provides the secure network connector, the KeyStore configuration and TLS configuration — for more details see this section.
Then, the https
module adds HTTP/1.1 as the protocol secured by TLS.
The $JETTY_BASE
directory looks like this:
$JETTY_BASE
├── resources
│ └── jetty-logging.properties
└── start.d
├── https.ini
└── ssl.ini
Note that the KeyStore file is missing, because you have to provide one with the cryptographic material you want (read this section to create your own KeyStore).
You need to configure these two properties by editing ssl.ini
:
-
jetty.sslContext.keyStorePath
-
jetty.sslContext.keyStorePassword
As a quick example, you can enable the test-keystore
module, that creates on-the-fly a KeyStore containing a self-signed certificate:
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
INFO : test-keystore initialized in ${jetty.base}/start.d/test-keystore.ini INFO : mkdir ${jetty.base}/lib/bouncycastle INFO : copy /path/to/maven.repository/org/bouncycastle/bcpkix-jdk15to18/1.78.1/bcpkix-jdk15to18-1.78.1.jar to ${jetty.base}/lib/bouncycastle/bcpkix-jdk15to18-1.78.1.jar INFO : copy /path/to/maven.repository/org/bouncycastle/bcprov-jdk15to18/1.78.1/bcprov-jdk15to18-1.78.1.jar to ${jetty.base}/lib/bouncycastle/bcprov-jdk15to18-1.78.1.jar INFO : copy /path/to/maven.repository/org/bouncycastle/bcutil-jdk15to18/1.78.1/bcutil-jdk15to18-1.78.1.jar to ${jetty.base}/lib/bouncycastle/bcutil-jdk15to18-1.78.1.jar INFO : Base directory was modified
The $JETTY_BASE
directory is now:
├── etc
│ └── test-keystore.p12
├── resources
│ └── jetty-logging.properties
└── start.d
├── https.ini
├── ssl.ini
└── test-keystore.ini
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:08.244:WARN :oejk.KeystoreGenerator:main: Generating Test Keystore: DO NOT USE IN PRODUCTION! 2024-10-01 18:01:10.375:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:10.632:INFO :oejus.SslContextFactory:main: x509=X509@2c1156a7(jetty-test-keystore,h=[localhost],a=[],w=[]) for Server@33fe57a9[provider=null,keyStore=file:///path/to/jetty.home-base/etc/test-keystore.p12,trustStore=null] 2024-10-01 18:01:10.929:INFO :oejs.AbstractConnector:main: Started ServerConnector@366c4480{SSL, (ssl, http/1.1)}{0.0.0.0:8443} 2024-10-01 18:01:10.948:INFO :oejs.Server:main: Started oejs.Server@53941c2f{STARTING}[12.0.15-SNAPSHOT,sto=5000] @5099ms
Note how Jetty is listening on port 8443
for the secure HTTP/1.1 protocol.
If you point your browser at This is normal because the certificate contained in |
Configuring HTTP/2
HTTP/2 is the successor of the HTTP/1.1 protocol, but it is quite different from HTTP/1.1: where HTTP/1.1 is a duplex, text-based protocol, HTTP/2 is a multiplex, binary protocol.
Because of these fundamental differences, a client and a server need to negotiate what version of the HTTP protocol they speak, based on what versions each side supports.
To ensure maximum compatibility, and reduce the possibility that an intermediary that only understands HTTP/1.1 will close the connection when receiving unrecognized HTTP/2 bytes, HTTP/2 is typically deployed over secure connections, using the TLS protocol to wrap HTTP/2.
Browsers only support secure HTTP/2. |
The protocol negotiation is performed by the ALPN TLS extension: the client advertises the list of protocols it can speak, and the server communicates to the client the protocol chosen by the server.
For example, you can have a client that only supports HTTP/1.1 and a server that supports both HTTP/1.1 and HTTP/2:
Nowadays, it’s common that both clients and servers support HTTP/2, so servers prefer HTTP/2 as the protocol to speak:
When you configure a connector with the HTTP/2 protocol, you typically want to also configure the HTTP/1.1 protocol. The reason to configure both protocols is that you typically do not control the clients: for example an old browser that does not support HTTP/2, or a monitoring console that performs requests using HTTP/1.1, or a heartbeat service that performs a single HTTP/1.0 request to verify that the server is alive.
Secure vs Clear-Text HTTP/2
Deciding whether you want to configure Jetty with secure HTTP/2 or clear-text HTTP/2 depends on your use case.
You want to configure secure HTTP/2 when Jetty is exposed directly to browsers, because browsers only support secure HTTP/2.
You may configure clear-text HTTP/2 (mostly for performance reasons) if you offload TLS at a load balancer (for example, HAProxy) or at a reverse proxy (for example, nginx).
You may configure clear-text HTTP/2 (mostly for performance reasons) to call microservices deployed to different Jetty servers (although you may want to use secure HTTP/2 for confidentiality reasons).
Secure HTTP/2
When you enable secure HTTP/2 you typically want to enable also secure HTTP/1.1, for backwards compatibility reasons: in this way, old browsers or other clients that do not support HTTP/2 will be able to connect to your server.
You need to enable:
-
the
ssl
Jetty module, which provides the secure connector and the KeyStore and TLS configuration -
the
http2
Jetty module, which adds ALPN handling and adds the HTTP/2 protocol to the secured connector -
optionally, the
https
Jetty module, which adds the HTTP/1.1 protocol to the secured connector
Use the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,http2,https
As when enabling the https
Jetty module, you need a valid KeyStore (read this section to create your own KeyStore).
As a quick example, you can enable the test-keystore
module, that creates on-the-fly a KeyStore containing a self-signed certificate:
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:14.910:WARN :oejk.KeystoreGenerator:main: Generating Test Keystore: DO NOT USE IN PRODUCTION! 2024-10-01 18:01:16.405:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:16.583:INFO :oejus.SslContextFactory:main: x509=X509@35d08e6c(jetty-test-keystore,h=[localhost],a=[],w=[]) for Server@53d102a2[provider=null,keyStore=file:///path/to/jetty.home-base/etc/test-keystore.p12,trustStore=null] 2024-10-01 18:01:16.759:INFO :oejs.AbstractConnector:main: Started ServerConnector@5d2a4eed{SSL, (ssl, alpn, h2, http/1.1)}{0.0.0.0:8443} 2024-10-01 18:01:16.777:INFO :oejs.Server:main: Started oejs.Server@736caf7a{STARTING}[12.0.15-SNAPSHOT,sto=5000] @4347ms
Note how Jetty is listening on port 8443
and the protocols supported are the sequence (ssl, alpn, h2, http/1.1)
.
The (ordered) list of protocols after alpn
are the application protocols, in the example above (h2, http/1.1)
.
When a new connection is accepted by the connector, Jetty first interprets the TLS bytes, then it handles the ALPN negotiation knowing that the application protocols are (in order) h2
and then http/1.1
.
You can customize the list of application protocols and the default protocol to use in case the ALPN negotiation fails by editing the alpn
module properties.
The HTTP/2 protocol parameters can be configured by editing the http2
module properties.
Clear-Text HTTP/2
When you enable clear-text HTTP/2 you typically want to enable also clear-text HTTP/1.1, for backwards compatibility reasons and to allow clients to upgrade from HTTP/1.1 to HTTP/2.
You need to enable:
-
the
http
Jetty module, which provides the clear-text connector and adds the HTTP/1.1 protocol to the clear-text connector -
the
http2c
Jetty module, which adds the HTTP/2 protocol to the clear-text connector
$ java -jar $JETTY_HOME/start.jar --add-modules=http,http2c
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:20.185:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:20.223:INFO :oejs.AbstractConnector:main: Started ServerConnector@3f670e35{HTTP/1.1, (http/1.1, h2c)}{0.0.0.0:8080} 2024-10-01 18:01:20.238:INFO :oejs.Server:main: Started oejs.Server@6a2b953e{STARTING}[12.0.15-SNAPSHOT,sto=5000] @2001ms
Note how Jetty is listening on port 8080
and the protocols supported are HTTP/1.1 and h2c
(i.e. clear-text HTTP/2).
With this configuration, browsers and client applications will be able to connect to port 8080
using:
-
HTTP/1.1 directly (e.g.
curl --http1.1 http://localhost:8080
):
GET / HTTP/1.1 Host: localhost:8080
-
HTTP/1.1 with upgrade to HTTP/2 (e.g.
curl --http2 http://localhost:8080
):
GET / HTTP/1.1 Host: localhost:8080 Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings:
-
HTTP/2 directly (e.g.
curl --http2-prior-knowledge http://localhost:8080
):
50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a 0d 0a 53 4d 0d 0a 0d 0a 00 00 12 04 00 00 00 00 00 00 03 00 00 00 64 00 04 40 00 00 00 00 02 00 00 00 00 00 00 1e 01 05 00 00 00 01 82 84 86 41 8a a0 e4 1d 13 9d 09 b8 f0 1e 07 7a 88 25 b6 50 c3 ab b8 f2 e0 53 03 2a 2f 2a
The HTTP/2 protocol parameters can be configured by editing the http2c
module properties.
HTTP/3
When you enable support for the HTTP/3 protocol, by default the secure HTTP/2 protocol is also enabled, so that browsers or clients that do not support HTTP/3 will be able to connect to your server.
You need to enable:
-
the
ssl
Jetty module, which provides the KeyStore and TLS configuration -
the
http3
Jetty module, which adds the HTTP/3 protocol on the HTTP/3 connector
Use the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,http3
Enabling any module Jetty module that supports secure network communication requires a valid KeyStore (read this section to create your own KeyStore), that, as a quick example, you can enable with the test-keystore
module, that creates on-the-fly a KeyStore containing a self-signed certificate:
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:24.328:WARN :oejk.KeystoreGenerator:main: Generating Test Keystore: DO NOT USE IN PRODUCTION! 2024-10-01 18:01:26.038:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:26.223:INFO :oejus.SslContextFactory:main: x509=X509@3ecd267f(jetty-test-keystore,h=[localhost],a=[],w=[]) for Server@58ffcbd7[provider=null,keyStore=file:///path/to/jetty.home-base/etc/test-keystore.p12,trustStore=null] 2024-10-01 18:01:26.381:INFO :oejs.AbstractConnector:main: Started ServerConnector@58ebfd03{SSL, (ssl, alpn, h2)}{0.0.0.0:8443} 2024-10-01 18:01:26.882:INFO :oejqs.QuicServerConnectionFactory:main: HTTP/3+QUIC support is experimental and not suited for production use. 2024-10-01 18:01:26.883:INFO :oejs.AbstractConnector:main: Started QuicServerConnector@40499e4f{h3, (h3)}{0.0.0.0:8444} 2024-10-01 18:01:26.909:INFO :oejs.Server:main: Started oejs.Server@42463763{STARTING}[12.0.15-SNAPSHOT,sto=5000] @4978ms
Note how Jetty is listening on port 8443
for HTTP/2 and on port 8444
for HTTP/3.
The HTTP/3 protocol parameters can be configured by editing the http3
module properties.
WebSocket
WebSocket is a network protocol for bidirectional data communication initiated via the HTTP/1.1 upgrade mechanism. WebSocket provides a simple, low-level, framing protocol layered over TCP. One or more WebSocket frames compose a WebSocket message that is either a UTF-8 text message or binary message.
Jetty provides an implementation of the following standards and specifications.
- RFC-6455 - The WebSocket Protocol
-
Jetty supports version 13 of the released and final specification.
- JSR-356 - The Java WebSocket API (
javax.websocket
) -
This is the official Java API for working with WebSockets.
- RFC-7692 - WebSocket Per-Message Deflate Extension
-
This is the replacement for perframe-compression, switching the compression to being based on the entire message, not the individual frames.
- RFC-8441 - Bootstrapping WebSockets with HTTP/2
-
Allows a single stream of an HTTP/2 connection to be upgraded to WebSocket. This allows one TCP connection to be shared by both protocols and extends HTTP/2’s more efficient use of the network to WebSockets.
Configuring WebSocket
Jetty provides two WebSocket implementations: one based on the Java WebSocket APIs defined by JSR 356, provided by module websocket-javax
, and one based on Jetty specific WebSocket APIs, provided by module websocket-jetty
.
The Jetty websocket
module enables both implementations, but each implementation can be enabled independently.
Remember that a WebSocket connection is always initiated from the HTTP protocol (either an HTTP/1.1 upgrade or an HTTP/2 connect), therefore to enable WebSocket you need to enable HTTP. |
To enable WebSocket support, you also need to decide what version of the HTTP protocol you want WebSocket to be initiated from, and whether you want secure HTTP.
For example, to enable clear-text WebSocket from HTTP/1.1, use the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http,websocket
To enable secure WebSocket from HTTP/2, use the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http2,websocket
When enabling secure protocols you need a valid KeyStore (read this section to create your own KeyStore).
As a quick example, you can enable the test-keystore
module, that creates on-the-fly a KeyStore containing a self-signed certificate:
$ java -jar $JETTY_HOME/start.jar --add-modules=test-keystore
To enable WebSocket on both HTTP/1.1 and HTTP/2, both clear-text and secure, use the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http,https,http2c,http2,websocket
Selectively Disabling WebSocket
Enabling the WebSocket Jetty modules comes with a startup cost because Jetty must perform two steps:
-
Scan web applications
*.war
files (and all the jars and classes inside it) looking for WebSocket EndPoints classes (either annotated with WebSocket API annotations or extending/implementing WebSocket API classes/interfaces). This can be a significant cost if your web application contains a lot of classes and/or jar files. -
Configure and wire WebSocket EndPoints so that WebSocket messages are delivered to the correspondent WebSocket EndPoint.
WebSocket support is by default enabled for all web applications.
For a specific web application, you can disable step 2 for Java WebSocket support (i.e. when the websocket-javax
module is enabled) by setting the context attribute org.eclipse.jetty.websocket.javax
to false
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- Disable Javax WebSocket -->
<context-param>
<param-name>org.eclipse.jetty.websocket.javax</param-name>
<param-value>false</param-value>
</context-param>
...
</web-app>
Furthermore, for a specific web application, you can disable step 1 (and therefore also step 2) as described in the annotations processing section.
Using WebSocket Client in WebApps
Web applications may need to use a WebSocket client to communicate with third party WebSocket services.
If the web application uses the Java WebSocket APIs, the WebSocket client APIs are provided by the Servlet Container and are available to the web application by enabling the WebSocket server APIs, and therefore you must enable the websocket-javax
Jetty module.
However, the Java WebSocket Client APIs are quite limited (for example, they do not support secure WebSocket). For this reason, web applications may want to use the Jetty WebSocket Client APIs.
When using the Jetty WebSocket Client APIs, web applications should include the required jars and their dependencies in the WEB-INF/lib
directory of the *.war
file.
Alternatively, when deploying your web applications in Jetty, you can enable the websocket-jetty-client
Jetty module to allow web applications to use the Jetty WebSocket Client APIs provided by Jetty, without the need to include jars and their dependencies in the *.war
file.
FastCGI
FastCGI is a network protocol primarily used by a web server to communicate to a FastCGI server.
FastCGI servers are typically used to serve web content generated by dynamic web languages, primarily PHP, but also Python, Ruby, Perl and others.
Web servers that supports FastCGI are, among others, Apache, Nginx, and Jetty. Web servers typically act as reverse proxies, converting HTTP requests that they receive from clients (browsers) to FastCGI requests that are forwarded to the FastCGI server. The FastCGI server spawns the dynamic web language interpreter, passing it the information contained in the FastCGI request and a dynamic web language script is executed, producing web content, typically HTML. The web content is then formatted into a FastCGI response that is returned to the web server, which converts it to an HTTP response that is then returned to the client.
The most well known FastCGI server is the PHP FastCGI Process Manager, or php-fpm
.
In the following we will assume that php-fpm
is used as FastCGI server.
This is a diagram of what described above:
Jetty can be configured to act as a web server that supports FastCGI, replacing the functionality that is normally provided by Apache or Nginx. This allows users to leverage Jetty features such as the support for HTTP/1.1, HTTP/2 and HTTP/3, Jetty’s scalability, and of course Jetty’s native support for Java Web Standards such as Servlets, JSPs, etc.
With such configuration, users can not only deploy their Java Web Applications in Jetty, but also serve their WordPress site or blog or their Drupal site without having to install and manage multiple web servers.
Configuring WordPress
This section explains how to configure Jetty to serve your WordPress site.
The prerequisites are:
-
Have
php-fpm
installed on your server host, and configured to listen either on a Unix-Domain socket (such as/run/php/php-fpm.sock
), or on a TCP socket (such aslocalhost:9000
). -
Have WordPress installed on your server host, for example under
/var/www/wordpress
. For more information about how to install WordPress and the necessary PHP modules, please refer to the WordPress Installation Guide.
Then, the secure HTTP and/or the secure HTTP/2 Jetty modules should be enabled to allow browsers to connect to Jetty.
Lastly, enable the fcgi-proxy
module to provide FastCGI support (to convert HTTP requests from browsers to FastCGI for php-fpm
and vice versa), and the core-deploy
module to deploy your WordPress web application as a Jetty context XML file.
For example:
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl,https,fcgi-proxy,core-deploy
The https Jetty module requires a KeyStore. If you do not already have one configured, you can add the test-keystore Jetty module to the command line above to create a KeyStore on-the-fly.
|
Now you can deploy a Jetty context XML file that represents your WordPress web application.
Use the following file as example, copy it as $JETTY_BASE/webapps/wordpress.xml
and customize it as necessary:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<New id="root" class="java.lang.String">
<Arg>/var/www/wordpress</Arg> (1)
</New>
<Set name="contextPath">/</Set> (2)
<Set name="baseResourceAsString"><Ref refid="root" /></Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.TryPathsHandler">
<Set name="originalPathAttribute">wordpress.originalPath</Set>
<Set name="originalQueryAttribute">wordpress.originalQuery</Set>
<Set name="paths">
<Call class="java.util.List" name="of">
<Arg>$path</Arg>
<Arg>/index.php</Arg>
</Call>
</Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.PathMappingsHandler">
<Call name="addMapping">
<Arg>
<New class="org.eclipse.jetty.http.pathmap.ServletPathSpec"><Arg>*.php</Arg></New>
</Arg>
<Arg>
<New class="org.eclipse.jetty.fcgi.proxy.FastCGIProxyHandler"> (3)
<Arg>(https?)://([^/]+)(.*)</Arg> (4)
<Arg>http://localhost:9000$3</Arg> (5)
<Arg><Ref refid="root" /></Arg>
<Set name="originalPathAttribute">wordpress.originalPath</Set>
<Set name="originalQueryAttribute">wordpress.originalQuery</Set>
<Set name="scriptPattern"><Call class="java.util.regex.Pattern" name="compile"><Arg>(.+?\\.php)</Arg></Call></Set>
<Set name="fastCGISecure">true</Set>
<Set name="unixDomainPath"><Call class="java.nio.file.Path" name="of"><Arg>/run/php/php-fpm.sock</Arg></Call></Set> (6)
</New>
</Arg>
</Call>
<Call name="addMapping">
<Arg>
<New class="org.eclipse.jetty.http.pathmap.ServletPathSpec"><Arg>/</Arg></New>
</Arg>
<Arg>
<New class="org.eclipse.jetty.server.handler.ResourceHandler"> (7)
<Set name="welcomeFiles"><Call class="java.util.List" name="of"><Arg>/index.php</Arg></Call></Set>
<Set name="welcomeMode"><Call class="org.eclipse.jetty.server.ResourceService$WelcomeMode" name="valueOf"><Arg>REHANDLE</Arg></Call></Set>
<Set name="dirAllowed">false</Set>
</New>
</Arg>
</Call>
</New>
</Set>
</New>
</Set>
</Configure>
1 | Specify the WordPress installation path. |
2 | Specify the context path of your web application. |
3 | The FastCGIProxyHandler forwards requests whose URI path matches *.php to php-fpm . |
4 | The client URI regex pattern to match. |
5 | The URI used to forward the request to php-fpm , where $3 is the 3rd matching group of the client URI regex pattern (int this example, the client URI path).
If php-fpm is configured to listen on a TCP socket, the host and port must match the listening TCP socket.
If php-fpm is configured to listen on a Unix-Domain socket, the host and port values are ignored but must be present. |
6 | If php-fpm is configured to listen on a Unix-Domain socket, specify the Unix-Domain socket path, otherwise omit this line. |
7 | The ResourceHandler serves static files from WordPress, such as *.css , *.js and image files. |
Now you can start Jetty and navigate to http://localhost:8080
with your browser to enjoy WordPress:
$ java -jar $JETTY_HOME/start.jar
Configuring Secure Protocols
Secure protocols are normal protocols such as HTTP/1.1, HTTP/2 or WebSocket that are wrapped by the TLS protocol. Any network protocol based on TCP can be wrapped with TLS.
QUIC, the protocol based on UDP that transports HTTP/3, uses TLS messages but not the TLS protocol framing.
The https
scheme used in URIs really means tls+http/1.1
(or tls+http/2
, or quic+http/3
) and similarly the wss
scheme used in URIs really means tls+websocket
, etc.
Senders wrap the underlying protocol bytes (e.g. HTTP bytes or WebSocket bytes) with the TLS protocol, while receivers first interpret the TLS protocol to obtain the underlying protocol bytes, and then interpret the wrapped bytes.
The ssl
Jetty module allows you to configure a secure network connector; if other modules require encryption, they declare a dependency on the ssl
module.
It is the job of other Jetty modules to configure the wrapped protocol.
For example, it is the https
module that configures the wrapped protocol to be HTTP/1.1.
Similarly, it is the http2
module that configures the wrapped protocol to be HTTP/2.
If you enable both the https
and the http2
module, you will have a single secure connector that will be able to interpret both HTTP/1.1 and HTTP/2.
Recall from the section about modules, that only modules that are explicitly enabled get their module configuration file (*.ini ) saved in $JETTY_BASE/start.d/ , and you want $JETTY_BASE/start.d/ssl.ini to be present so that you can configure the connector properties, the KeyStore properties and the TLS properties.
|
Customizing KeyStore and SSL/TLS Configuration
Secure protocols have a slightly more complicated configuration since they require to configure a KeyStore. Refer to the KeyStore section for more information about how to create and manage a KeyStore.
For simple cases, you only need to configure the KeyStore path and KeyStore password as explained in this section.
For more advanced configuration you may want to configure the TLS protocol versions, or the ciphers to include/exclude, etc.
The correct way of doing this is to create a custom Jetty XML file and reference it in $JETTY_BASE/start.d/ssl.ini
:
jetty.sslContext.keyStorePassword=my_passwd! (1)
etc/tls-config.xml (2)
1 | Configures the jetty.sslContext.keyStorePassword property with the KeyStore password. |
2 | References your newly created $JETTY_BASE/etc/tls-config.xml . |
The ssl.ini
file above only shows the lines that are not commented out (you can leave the lines that are commented unmodified for future reference).
You want to create the $JETTY_BASE/etc/tls-config.xml
with the following template content:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
... (1)
</Ref>
</Configure>
1 | Here goes your advanced configuration. |
The tls-config.xml
file references the sslContextFactory
component (created by the ssl
Jetty module) that configures the KeyStore and TLS parameters, so that you can now call its APIs via XML, and you will have full flexibility for any advanced configuration you want (see below for few examples).
Refer to the SslContextFactory.Server javadocs for the list of methods that you can call through the Jetty XML file.
Use module properties whenever possible, and only resort to use a Jetty XML file for advanced configuration that you cannot do using module properties. |
Customizing SSL/TLS Protocol Versions
By default, the SSL protocols (SSL, SSLv2, SSLv3, etc.) are already excluded because they are vulnerable. To explicitly add the exclusion of TLSv1.0 and TLSv1.1 (that are also vulnerable — which leaves only TLSv1.2 and TLSv1.3 available), you want to use this XML:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
<Call name="addExcludeProtocols">
<Arg>
<Array type="String">
<Item>TLSv1.0</Item>
<Item>TLSv1.1</Item>
</Array>
</Arg>
</Call>
</Ref>
</Configure>
Customizing SSL/TLS Ciphers
You can precisely set the list of excluded ciphers, completely overriding Jetty’s default, with this XML:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">
<Configure>
<Ref refid="sslContextFactory">
<Set name="ExcludeCipherSuites">
<Array type="String">
<Item>^TLS_RSA_.*$</Item>
<Item>^.*_RSA_.*_(MD5|SHA|SHA1)$</Item>
<Item>^.*_DHE_RSA_.*$</Item>
<Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
<Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
<Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
</Array>
</Set>
</Ref>
</Configure>
Note how each array item specifies a regular expression that matches multiple ciphers, or specifies a precise cipher to exclude.
You can choose to create multiple XML files, and reference them all from $JETTY_BASE/start.d/ssl.ini
, or put all your custom configurations in a single XML file.
Renewing the Certificates
When you create a certificate, you must specify for how many days it is valid.
The typical validity is 90 days, and while this period may seem short, it has two advantages:
-
Reduces the risk in case of compromised/stolen keys.
-
Encourages automation, i.e. certificate renewal performed by automated tools (rather than manually) at scheduled times.
To renew a certificate, you must go through the same steps you followed to create the certificate the first time, and then you can reload the KeyStore without the need to stop Jetty.
Watching and Reloading the KeyStore
Jetty can be configured to monitor the directory of the KeyStore file, and reload the SslContextFactory
component if the KeyStore file changed.
This feature can be enabled by activating the ssl-reload
Jetty module:
$ java -jar $JETTY_HOME/start.jar --add-modules=ssl-reload
For more information about the configuration of the ssl-reload
Jetty module, see this section.
Using Conscrypt as SSL/TLS Provider
If not explicitly configured, the TLS implementation is provided by the JDK you are using at runtime.
OpenJDK’s vendors may replace the default TLS provider with their own, but you can also explicitly configure an alternative TLS provider.
The standard TLS provider from OpenJDK is implemented in Java (no native code), and its performance is not optimal, both in CPU usage and memory usage.
A faster alternative, implemented natively, is Google’s Conscrypt, which is built on BoringSSL, which is Google’s fork of OpenSSL.
As Conscrypt eventually binds to a native library, there is a higher risk that a bug in Conscrypt or in the native library causes a JVM crash, while the Java implementation will not cause a JVM crash. |
To use Conscrypt as the TLS provider just enable the conscrypt
Jetty module:
$ java -jar $JETTY_HOME/start.jar --add-modules=conscrypt
Configuring SNI
Server Name Indication (SNI) is a TLS extension that clients send to indicate what domain they want to connect to during the initial TLS handshake.
Modern TLS clients (e.g. browsers) always send the SNI extension; however, older TLS clients may not send the SNI extension.
Being able to handle the SNI is important when you have virtual hosts and a KeyStore with multiple certificates, one for each domain.
For example, you may have deployed over a secure connector two web applications, both at context path /
, one at virtual host one.com
and one at virtual host two.net
.
The KeyStore contains two certificates, one for one.com
and one for two.net
.
There are three ssl
module properties that control the SNI behavior on the server: one that works at the TLS level, and two that works at the HTTP level.
The property that works at the TLS level is:
jetty.sslContext.sniRequired
-
Whether SNI is required at the TLS level, defaults to
false
. Its behavior is explained by the following table:Table 1. Behavior of the jetty.sslContext.sniRequired
propertysniRequired=false
sniRequired=true
SNI =
null
client receives default certificate
client receives TLS failure
SNI =
wrong.org
client receives default certificate
client receives TLS failure
SNI =
one.com
client receives
one.com
certificateclient receives
one.com
certificateThe default certificate is the certificate returned by the TLS implementation in case there is no SNI match, and you should not rely on this certificate to be the same across Java vendors and versions, or Jetty versions, or TLS provider vendors and versions.
In the example above it could be either the
one.com
certificate or thetwo.net
certificate.
When jetty.sslContext.sniRequired=true
, clients that don’t send a valid SNI receive a TLS failure, and their attempt to connect to the server fails.
The details of this failure may not be reported and could be difficult to figure out that the failure is related to an invalid SNI.
For this reason, other two properties are defined at the HTTP level, so that clients can received an HTTP 400 response with more details about what went wrong while trying to connect to the server:
jetty.ssl.sniRequired
-
Whether SNI is required at the HTTP level, defaults to
false
. Its behavior is similar to thejetty.sslContext.sniRequired
property above, and is explained by the following table:Table 2. Behavior of the jetty.ssl.sniRequired
propertysniRequired=false
sniRequired=true
SNI =
null
Accept
Reject: 400 Bad Request
SNI =
wrong.org
Accept
Reject: 400 Bad Request
SNI =
one.com
Accept
Accept
When jetty.ssl.sniRequired=true
, the SNI is matched against the certificate sent to the client, and only if there is a match the request is accepted.
When the request is accepted, there could be an additional check controlled by the following property:
jetty.ssl.sniHostCheck
-
Whether the certificate sent to the client matches the
Host
header, defaults totrue
. Its behavior is explained by the following table:Table 3. Behavior of the jetty.ssl.sniHostCheck
propertysniHostCheck=false
sniHostCheck=true
certificate =
one.com
Host: wrong.org
Accept
Reject: 400 Bad Request
certificate =
one.com
Host: one.com
Accept
Accept
In the normal case with the default server configuration, for a TLS clients that sends SNI, and then sends an HTTP request with the correct Host
header, Jetty will pick the correct certificate from the KeyStore based on the SNI received from the client, and accept the request.
Accepting the request does not mean that the request is responded with an HTTP 200 OK, but just that the request passed successfully the SNI checks and will be processed by the server. If the request URI is for a resource that does not exist, the response will likely be a 404 Not Found.
You may modify the default values of the SNI properties if you want stricter control over old/broken TLS clients or bad HTTP requests.
Jetty Behind a Load Balancer or Reverse Proxy
You may need to configure one or more Jetty instances behind an intermediary, typically a load balancer such as HAProxy, or a reverse proxy such as Apache HTTP Server or Nginx.
HAProxy can communicate either HTTP/1.1 or HTTP/2 to backend servers such as Jetty. Apache HTTP Server and Nginx can only speak HTTP/1.1 to backend servers such as Jetty, and have no plans to support HTTP/2 towards backend servers. |
In these setups, typically the proxy performs TLS offloading, and the communication with backend servers happens in clear-text. It is possible, however, to configure the proxy so that all the bytes arriving from the client are tunnelled opaquely to the backend Jetty server (that therefore needs to perform the TLS offloading) and vice versa the bytes arriving from the Jetty server are tunnelled opaquely to the client.
Also in these setups, the TCP/IP connection terminating on the Jetty servers does not originate from the client, but from the proxy, so that the remote IP address and port number may be reported incorrectly in backend server logs, or worse applications may not work because they need to be able to differentiate different clients based on the client IP address.
For this reason, intermediaries typically implement at least one of several de facto standards to communicate information about the original client connection to the backend Jetty server.
Jetty supports two methods to process client information sent by intermediaries:
-
The
Forwarded
HTTP header, defined in RFC 7239 and replacing the oldX-Forwarded-*
headers, defined in this section. -
The Proxy Protocol, defined in this section.
In both methods, web applications that call HttpServletRequest.getRemoteAddr()
will receive the remote client IP address as specified by the client information sent by the intermediary, not the physical IP address of TCP connection with the intermediary.
Likewise, HttpServletRequest.getRemotePort()
will return the remote client IP port as specified by the client information sent by the intermediary, and HttpServletRequest.isSecure()
will return whether the client made a secure request using the https
scheme as specified by the client information sent by the intermediary.
Configuring the Forwarded Header
The Forwarded
HTTP header is added by the intermediary with information about the client and the client request, for example:
GET / HTTP/1.1 Host: domain.com Forwarded: for=2.36.72.144:21216;proto=https
In the example above, the intermediary added the Forwarded
header specifying that the client remote address is 2.36.72.144:21216
and that the request was made with the https
scheme.
Let’s assume you have already configured Jetty with the HTTP/1.1 protocol with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http
Support for the Forwarded
HTTP header (and its predecessor X-Forwarded-*
headers) is enabled with the http-forwarded
Jetty module:
$ java -jar $JETTY_HOME/start.jar --add-modules=http-forwarded
INFO : http-forwarded initialized in ${jetty.base}/start.d/http-forwarded.ini INFO : Base directory was modified
With the http-forwarded
Jetty module enabled, Jetty interprets the Forwarded
header and makes its information available to web applications via the standard Servlet APIs.
For further information about configuring the http-forwarded
Jetty module, see this section.
Configuring the Proxy Protocol
The Proxy Protocol is the de facto standard, introduced by HAProxy, to communicate client information to backend servers via the TCP connection, rather than via HTTP headers.
The information about the client connection is sent as a small data frame on each newly established connection. This mechanism is therefore independent of any protocol, so it can be used for TLS, HTTP/1.1, HTTP/2, etc.
There are 2 versions of the proxy protocol: v1 and v2, both supported by Jetty. Proxy protocol v1 is human readable, but it only carries information about the client TCP connection (IP address and IP port). Proxy protocol v2 has a binary format, carries the information about the client TCP connection, and can carry additional arbitrary information encoded in pairs |
Support for the proxy protocol can be enabled for the clear-text connector or for the secure connector (or both).
Let’s assume you have already configured Jetty with the HTTP/1.1 clear-text protocol with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=http
To enable proxy protocol support for the clear-text connector, enable the proxy-protocol
Jetty module:
$ java -jar $JETTY_HOME/start.jar --add-modules=proxy-protocol
INFO : proxy-protocol initialized in ${jetty.base}/start.d/proxy-protocol.ini INFO : Base directory was modified
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:33.683:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:33.751:INFO :oejs.AbstractConnector:main: Started ServerConnector@6ad54a8c{[proxy], ([proxy], http/1.1)}{0.0.0.0:8080} 2024-10-01 18:01:33.765:INFO :oejs.Server:main: Started oejs.Server@139982de{STARTING}[12.0.15-SNAPSHOT,sto=5000] @1947ms
Note how in the example above the list of protocols for the clear-text connector is first proxy
and then http/1.1
.
For every new TCP connection, Jetty first interprets the proxy protocol bytes with the client information; after this initial proxy protocol processing, Jetty interprets the incoming bytes as HTTP/1.1 bytes.
Enabling proxy protocol support for the secure connector is similar.
Let’s assume you have already configured Jetty with the HTTP/1.1 secure protocol and the test KeyStore with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=https,test-keystore
Enable the proxy-protocol-ssl
Jetty module with the following command (issued from within the $JETTY_BASE
directory):
$ java -jar $JETTY_HOME/start.jar --add-modules=proxy-protocol-ssl
INFO : proxy-protocol-ssl initialized in ${jetty.base}/start.d/proxy-protocol-ssl.ini INFO : Base directory was modified
Starting Jetty yields:
$ java -jar $JETTY_HOME/start.jar
2024-10-01 18:01:39.568:WARN :oejk.KeystoreGenerator:main: Generating Test Keystore: DO NOT USE IN PRODUCTION! 2024-10-01 18:01:41.199:INFO :oejs.Server:main: jetty-12.0.15-SNAPSHOT; built: 2024-10-01T17:53:16.597Z; git: e9d61a16862f18d4c20f422e490e27e66c38d684; jvm 23+37 2024-10-01 18:01:41.360:INFO :oejus.SslContextFactory:main: x509=X509@a22cb6a(jetty-test-keystore,h=[localhost],a=[],w=[]) for Server@5dd1c9f2[provider=null,keyStore=file:///path/to/jetty.home-base/etc/test-keystore.p12,trustStore=null] 2024-10-01 18:01:41.608:INFO :oejs.AbstractConnector:main: Started ServerConnector@4097cac{[proxy], ([proxy], ssl, http/1.1)}{0.0.0.0:8443} 2024-10-01 18:01:41.648:INFO :oejs.Server:main: Started oejs.Server@4ed5eb72{STARTING}[12.0.15-SNAPSHOT,sto=5000] @4415ms
Note how in the example above the list of protocols for the secure connector is first proxy
, then ssl
and then http/1.1
.
HAProxy and Jetty with HTTP/1.1 and HTTP/2
HAProxy is an open source solution that offers load balancing and proxying for TCP and HTTP based application, and can be used as a replacement for Apache or Nginx when these are used as reverse proxies.
The deployment proposed here has HAProxy playing the role that Apache and Nginx usually do: to perform the TLS offloading (that is, decrypt incoming bytes and encrypt outgoing bytes) and then forwarding the now clear-text traffic to a backend Jetty server, speaking either HTTP/1.1 or HTTP/2. Since HAProxy’s TLS offloading is based on OpenSSL, it is much more efficient than the Java implementation shipped with OpenJDK.
After you have installed HAProxy on your system, you want to configure it so that it can perform TLS offloading.
HAProxy will need a single file containing the X509 certificates and the private key, all in PEM format, with the following order:
-
The site certificate; this certificate’s Common Name refers to the site domain (for example: CN=*.webtide.com) and is signed by Certificate Authority #1.
-
The Certificate Authority #1 certificate; this certificate may be signed by Certificate Authority #2.
-
The Certificate Authority #2 certificate; this certificate may be signed by Certificate Authority #3; and so on until the Root Certificate Authority.
-
The Root Certificate Authority certificate.
-
The private key corresponding to the site certificate.
Refer to the section about KeyStores for more information about generating the required certificates and private key.
Now you can create the HAProxy configuration file (in Linux it’s typically /etc/haproxy/haproxy.cfg
).
This is a minimal configuration:
global
tune.ssl.default-dh-param 1024
defaults
timeout connect 10000ms
timeout client 60000ms
timeout server 60000ms
frontend fe_http (1)
mode http
bind *:80
# Redirect to https
redirect scheme https code 301
frontend fe_https (2)
mode tcp
bind *:443 ssl no-sslv3 crt /path/to/domain.pem ciphers TLSv1.2 alpn h2,http/1.1
default_backend be_http
backend be_http (3)
mode tcp
server domain 127.0.0.1:8282 send-proxy-v2
1 | The fe_http front-end accepts connections on port 80 and redirects them to use the https scheme. |
2 | The fe_https front-end accepts connections on port 443, and it is where the TLS decryption/encryption happens.
You must specify the path to the PEM file containing the TLS key material (the crt /path/to/domain.pem part), the ciphers that are suitable for HTTP/2 (ciphers TLSv1.2 ), and the ALPN protocols supported (alpn h2,http/1.1 ).
This front-end then forwards the now decrypted bytes to the backend in mode tcp .
The mode tcp says that HAProxy will not try to interpret the bytes but instead opaquely forwards them to the backend. |
3 | The be_http backend will forward (again in mode tcp ) the clear-text bytes to a Jetty connector that talks clear-text HTTP/2 and HTTP/1.1 on port 8282.
The send-proxy-v2 directive sends the proxy protocol v2 bytes to the backend server. |
On the Jetty side, you need to enable the following modules:
$ java -jar $JETTY_HOME/start.jar --add-modules=proxy-protocol,http2c,http,deploy
You need to specify the host (127.0.0.1
) and port (8282
) you have configured in HAProxy when you start Jetty:
$ java -jar $JETTY_HOME/start.jar jetty.http.host=127.0.0.1 jetty.http.port=8282
You want the Jetty connector that listens on port For this reason, you want to specify the If your Jetty instance runs on a different machine and/or on a different (sub)network, you may want to adjust both the back-end section of the HAProxy configuration file and the |
With this configuration for HAProxy and Jetty, browsers supporting HTTP/2 will connect to HAProxy, which will decrypt the traffic and send it to Jetty. Likewise, HTTP/1.1 clients will connect to HAProxy, which will decrypt the traffic and send it to Jetty.
The Jetty connector, configured with the http2c
and the http
modules is able to distinguish whether the incoming bytes are HTTP/2 or HTTP/1.1 and will handle the request accordingly.
The response is relayed back to HAProxy, which will encrypt it and send it back to the remote client.
This configuration offers you efficient TLS offloading, HTTP/2 support and transparent fallback to HTTP/1.1 for clients that don’t support HTTP/1.1.