Applications Azure

Eclipse Jetty 12 on Ubuntu 24.04 on Azure User Guide

| Product: Eclipse Jetty 12 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Eclipse Jetty 12 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Jetty is a lightweight, embeddable Java HTTP server and Jakarta Servlet/JSP container — the runtime teams reach for when Tomcat or WildFly are heavier than a workload needs, and a common embedded app server behind Java microservices.

The image installs the official jetty-home 12.0.37 distribution (Jakarta EE10 / Servlet 6.0 environment) from Maven Central, pinned by exact version and verified against a sha256 checksum. It runs on OpenJDK 21 (Jetty 12 requires Java 17+). Because Jetty is a web application server, this image ships a genuinely browsable web UI: a cloudimg-branded landing page at the server root plus Jetty's own standard demo web applications pre-deployed, so you can point a browser at the VM and click through real, working example apps — a Hello-World servlet, request/session/cookie dump servlets, WebSocket and Comet chat demos, and JSP/JSTL examples.

What is included:

  • Eclipse Jetty 12.0.37 (jetty-home distribution) from the official Maven Central release, sha256-pinned

  • OpenJDK 21 JRE headless (Jetty 12 requires Java 17+)

  • jetty.service systemd unit auto-starting on boot, running as the unprivileged jetty:jetty system user

  • eclipse-jetty-firstboot.service systemd oneshot that starts Jetty + nginx and confirms the demo webapps answer before completing

  • HTTP connector bound to loopback only (127.0.0.1:8080) — nginx fronts it on :80

  • Unauthenticated /healthz endpoint (nginx-native, HTTP 200) for load balancer / probe checks

  • A cloudimg-branded root landing page at context / that renders a live server timestamp and request counter (proof the JSP container executes per request) and links out to the demo apps

  • Jetty's own demo web applications pre-deployed: the flagship demo webapp at /ee10-test/ and the JSP/JSTL demo webapp at /ee10-demo-jsp/

  • JETTY_BASE=/var/lib/jetty on a dedicated 20 GiB Azure data disk — webapps, logs and any deployed application state survive independently of the OS disk

  • Ubuntu 24.04 LTS base with latest security patches applied at build time

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the Eclipse Jetty 12 listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development. Production Servlet workloads should use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger.

Step 1: Deploy from the Azure Portal

Search Eclipse Jetty 12 in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin), TCP 80 (HTTP), TCP 443 (reserved for TLS) from your client networks.

Step 2: Deploy from the Azure CLI

RG="jetty-prod"; LOCATION="eastus"; VM_NAME="jetty-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/eclipse-jetty-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name jetty-vnet --address-prefix 10.100.0.0/16 --subnet-name jetty-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name jetty-nsg
az network nsg rule create -g "$RG" --nsg-name jetty-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name jetty-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name jetty-nsg --name allow-https --priority 120 \
  --destination-port-ranges 443 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name jetty-vnet --subnet jetty-subnet --nsg jetty-nsg --public-ip-sku Standard

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

jetty.service, nginx.service and eclipse-jetty-firstboot.service all start automatically on first boot — there is no admin account to set up, because Jetty has no login of its own.

Step 4: Verify the Jetty and nginx Services

sudo systemctl status jetty.service --no-pager
sudo systemctl status nginx.service --no-pager
sudo test -f /var/lib/cloudimg/eclipse-jetty-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tln | grep -E ':(80|8080) '

Expected output:

● jetty.service - Eclipse Jetty Servlet Container
     Active: active (running)
     Main PID: 3661 (java)
     ...Started ServerConnector@...{HTTP/1.1, (http/1.1)}{127.0.0.1:8080}

● nginx.service - A high performance web server and a reverse proxy server
     Active: active (running)

FIRSTBOOT_DONE
LISTEN 0      511               0.0.0.0:80        0.0.0.0:*
LISTEN 0      50     [::ffff:127.0.0.1]:8080            *:*
LISTEN 0      511                  [::]:80           [::]:*

Notice the HTTP connector is bound to 127.0.0.1:8080 only — Jetty is never directly reachable from the network; nginx on :80 is the only public entry point.

jetty.service and nginx.service both active (running); firstboot sentinel present; ss shows the Jetty connector bound to 127.0.0.1:8080 only, with nginx public on :80

Step 5: Check the Health Endpoint

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz

Expected output: 200. This unauthenticated endpoint is served directly by nginx (not proxied to Jetty) so it always answers even under Servlet-container load — use it for a load balancer or uptime probe.

Step 6: Open the Jetty Landing Page in a Browser

Point a browser at the VM's public address (http://<vm-ip>/, NSG must allow TCP 80). The image serves a cloudimg-branded landing page that is itself rendered by Jetty's JSP servlet container — the server timestamp and hit count are recomputed on every request, and the page links out to Jetty's demo web applications.

The cloudimg Eclipse Jetty landing page in a browser: a live server timestamp and per-request hit count rendered by the JSP container, with cards linking to the Jetty and JSP demo web applications

You can prove the per-request execution from the shell too — request the root twice and watch both values change:

curl -s http://127.0.0.1/ | grep -oE '(Server time:|Hit count \(this JVM\):)</strong>[^<]*'
curl -s http://127.0.0.1/ | grep -oE '(Server time:|Hit count \(this JVM\):)</strong>[^<]*'

Expected output — the timestamp and hit count both change on every request:

Server time:</strong> 2026-07-09T19:58:11.578448807Z
Hit count (this JVM):</strong> 1
Server time:</strong> 2026-07-09T19:58:12.031117662Z
Hit count (this JVM):</strong> 2

This proves the Servlet/JSP container is genuinely executing code on every request — a request-scoped counter and a live server timestamp cannot come from a cached or static file. This is the actual value a Servlet container adds over a plain web server like nginx or Apache HTTPD on its own.

curl http://127.0.0.1/ run twice: the demo JSP landing page's Server time and Hit count both change between requests, proving live per-request execution

Step 7: Explore Jetty's Demo Web Applications

The image pre-deploys Jetty's own standard demo webapps so you can see the container running real applications. Open them in a browser and click through the examples.

The flagship Jetty demo webapp is served at http://<vm-ip>/ee10-test/. It exercises a Hello-World servlet, request/session/cookie dump servlets, async responses, a Comet long-polling chat and WebSocket examples:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/ee10-test/

Expected output: 200.

Jetty's flagship demo web application at /ee10-test/ open in a browser: the "Eclipse Jetty Demo Webapp" index listing Hello-World, request/session/cookie dump servlets, Comet chat and WebSocket examples, with the "Powered by Jetty" badge

Click the Request dump servlet (/ee10-test/dump/info) to see Jetty introspect the live HTTP request — method, headers, scheme, session id and more — all produced by a running servlet:

The Jetty Dump Servlet at /ee10-test/dump/info rendered in a browser, listing the live HttpServletRequest properties (method, context path, scheme, server name, session id) computed by the servlet on this request

The JSP & JSTL demo webapp at http://<vm-ip>/ee10-demo-jsp/ shows the JSP engine compiling and executing pages on the fly (JSP dump, JSTL iteration, expression-language examples):

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/ee10-demo-jsp/

Expected output: 200.

Jetty's JSP & JSTL demo web application at /ee10-demo-jsp/ open in a browser: the "Eclipse Jetty JSP Demo Webapp" index listing JSP-with-Beans, BodyTag, SimpleTag, Tag File and JSTL expression examples, with the date rendered dynamically by the JSP engine

Step 8: Confirm the Jetty Version and Deployed Webapps

head -1 /opt/jetty/VERSION.txt
ls -1 /var/lib/jetty/webapps/

Expected output:

12.0.37 - 27 June 2026
ee10-demo-jetty.d
ee10-demo-jetty.war
ee10-demo-jetty.xml
ee10-demo-jsp.war
root

The root directory maps to context path / (Jetty's convention, matching the demo-root module shipped in jetty-home itself); ee10-demo-jetty.war deploys at /ee10-test (see its ee10-demo-jetty.xml context descriptor) and ee10-demo-jsp.war at /ee10-demo-jsp.

jetty-home VERSION.txt confirms 12.0.37; the pre-deployed webapps under /var/lib/jetty/webapps/ (root landing app plus the ee10-demo-jetty and ee10-demo-jsp demo WARs) each answering HTTP 200

Step 9: Read the Endpoint Notes

At first boot, eclipse-jetty-firstboot.service writes a non-secret notes file documenting the deployed URLs (Jetty has no credentials to rotate):

sudo cat /var/lib/cloudimg/eclipse-jetty-endpoints.notes

Expected output:

# Eclipse Jetty — Endpoint Notes
# Generated on first boot by eclipse-jetty-firstboot.service
#
# Jetty has no admin UI or login — there is nothing to authenticate to.
#
JETTY_DEMO_URL=http://<vm-ip>/
JETTY_DEMO_WEBAPP_URL=http://<vm-ip>/ee10-test/
JETTY_JSP_DEMO_URL=http://<vm-ip>/ee10-demo-jsp/
JETTY_HEALTHZ_URL=http://<vm-ip>/healthz
JETTY_HOME=/opt/jetty
JETTY_BASE=/var/lib/jetty
DEPLOY_YOUR_OWN_WAR=Drop a .war file into /var/lib/jetty/webapps/ — Jetty hot-deploys it.

cat /var/lib/cloudimg/eclipse-jetty-endpoints.notes shows the landing, demo-webapp, JSP-demo and healthz URLs, JETTY_HOME/JETTY_BASE paths, and the one-line WAR hot-deploy instruction

Step 10: Deploy Your Own WAR

Jetty's ee10-deploy module watches $JETTY_BASE/webapps/ and hot-deploys any WAR file dropped into it — no restart required:

# Build your WAR (locally)
mvn package -DskipTests
# Copy to the VM
scp target/myapp.war azureuser@<vm-ip>:/tmp/
# Deploy (hot-deploy, no restart needed)
ssh azureuser@<vm-ip> 'sudo mv /tmp/myapp.war /var/lib/jetty/webapps/'
# Jetty auto-detects and deploys within a few seconds; visit:
# http://<vm-ip>/myapp/

To deploy at context path / instead (replacing the demo landing page), remove /var/lib/jetty/webapps/root/ first, then drop in a WAR or exploded directory named root. You can likewise remove the ee10-demo-jetty* and ee10-demo-jsp.war demo webapps from /var/lib/jetty/webapps/ once you no longer need them.

Step 11: Server Components

Component Path
JETTY_HOME (binaries, immutable) /opt/jetty
JETTY_BASE (data disk) /var/lib/jetty
Webapps /var/lib/jetty/webapps/
Root landing webapp /var/lib/jetty/webapps/root/index.jsp
Jetty demo webapp (/ee10-test) /var/lib/jetty/webapps/ee10-demo-jetty.war
JSP demo webapp (/ee10-demo-jsp) /var/lib/jetty/webapps/ee10-demo-jsp.war
Start modules /var/lib/jetty/start.d/
Logs systemd journal (journalctl -u jetty.service); /var/lib/jetty/logs/ if the requestlog module is enabled
Systemd unit /etc/systemd/system/jetty.service
Firstboot script /usr/local/sbin/eclipse-jetty-firstboot.sh
Firstboot service /etc/systemd/system/eclipse-jetty-firstboot.service
Endpoint notes /var/lib/cloudimg/eclipse-jetty-endpoints.notes (mode 0644)
Firstboot sentinel /var/lib/cloudimg/eclipse-jetty-firstboot.done
nginx vhost /etc/nginx/sites-available/cloudimg-jetty

Step 12: Managing the Jetty Service

sudo systemctl status jetty.service --no-pager
sudo systemctl restart jetty.service
sudo journalctl -u jetty.service -n 50 --no-pager

By default Jetty's stdout/stderr go to the systemd journal (journalctl -u jetty.service), not a file under $JETTY_BASE/logs/. Use sudo journalctl -u jetty.service -f to follow the log live. To also write a request/access log to a file, enable Jetty's requestlog module (sudo -u jetty java -jar /opt/jetty/start.jar --add-modules=requestlog, from /var/lib/jetty), which writes to $JETTY_BASE/logs/.

Enabling additional Jetty modules (WebSocket, HTTP/2, SSL termination at Jetty itself, etc.) is a one-line --add-modules from $JETTY_BASE:

cd /var/lib/jetty
sudo -u jetty java -jar /opt/jetty/start.jar --add-modules=http2c
sudo systemctl restart jetty.service

Step 13: Security Recommendations

  • Never expose port 8080 directly — it is bound to loopback only by design; always go through nginx (:80) or add a TLS-terminating reverse proxy on :443

  • Add TLS by fronting nginx with Let's Encrypt/Certbot, or terminate TLS in Jetty itself via the ssl module and a certificate on the data disk

  • Remove the demo webapps before production if you do not need them — delete ee10-demo-jetty* and ee10-demo-jsp.war from /var/lib/jetty/webapps/. The bundled Jetty demo login realm is used only by the demo webapp's protected example pages and grants no OS access, but a lean production image should carry only your own application