Deployment guide

WildFly 40

Overview

WildFly is the upstream open-source Jakarta EE application server, the engine behind Red Hat's commercial JBoss EAP. Version 40 implements Jakarta EE 11 and runs on OpenJDK 21.

This guide covers deploying WildFly 40 on Ubuntu 24.04 LTS on Azure. The image installs WildFly 40.0.1.Final from the official GitHub release tarball, verified against the published SHA-256 checksum. On the first boot of every instance, wildfly-firstboot.service generates fresh passwords for both built-in security realms, starts the server and writes the credentials to a root-only file.

What you get:

  • WildFly 40.0.1.Final from the official GitHub release tarball, SHA-256 verified
  • OpenJDK 21 (full JDK, so add-user.sh and jboss-cli.sh both work)
  • wildfly.service running as the unprivileged wildfly user with the standalone-full.xml profile (web, EJB, JMS, JPA and web services)
  • An nginx reverse proxy serving the application on port 80
  • wildfly-firstboot.service provisioning unique per-instance credentials
  • A JVM heap tuned for a 4 GB VM (256 MB initial, 1024 MB maximum)
  • An Ubuntu 24.04 LTS base, patched at build time, with unattended security upgrades enabled
  • 24/7 SecureAnyCloud support with a 24-hour response SLA

Key facts:

Item Value

Default login user azureuser

Application URL http://<vm-ip>/ (nginx on port 80)

Management console http://127.0.0.1:9990/console (loopback only, reached over an SSH tunnel)

Credentials file /stage/scripts/wildfly-credentials.log (mode 0600, root only)

JBOSS_HOME /opt/wildfly

License Apache License 2.0

Prerequisites

  • An active Azure subscription
  • A subscription to the WildFly 40 listing in the Azure Marketplace
  • An SSH key pair
  • A virtual network and subnet in your target region

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) for development and test. For production, use Standard_D4s_v5 (4 vCPU, 16 GB RAM) and raise the heap as described in Step 12, Tuning the JVM Heap.

1

Deploy from the Azure Portal

Search for WildFly 40 in the Azure Marketplace, select the SecureAnyCloud offering and click Create. Configure the network security group to allow TCP 22 for SSH and TCP 80 for HTTP from your own client networks.

Do not open port 9990. The management console deliberately binds to the loopback interface only and is reached over an SSH tunnel, so it is never exposed to the internet.

2

Deploy from the Azure CLI

RG="wildfly-prod"; LOCATION="eastus"; VM_NAME="wildfly-01" IMAGE_URN="sac1647283583153:wildfly-40-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name wf-vnet --address-prefix 10.101.0.0/16 \
  --subnet-name wf-subnet --subnet-prefix 10.101.1.0/24 az network nsg create -g "$RG" --name wf-nsg az network nsg rule create -g "$RG" --nsg-name wf-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 wf-nsg --name allow-http --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:wildfly-40-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name wf-vnet --subnet wf-subnet --nsg wf-nsg --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>

Both wildfly.service and nginx.service start automatically once first-boot initialization has finished. On a Standard_B2s, this takes roughly 30 to 60 seconds from power on.

4

Verify the Services

sudo systemctl status wildfly.service nginx.service --no-pager

Both units report active (running). The WildFly unit shows the standalone-full.xml profile and the tuned heap flags on its command line.

Confirm that first-boot initialization completed, and check the listeners:

sudo test -f /var/lib/sac/data/wildfly-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tln | grep -E ':(80|8080|9990) '

Expected output:

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

Port 9990 is bound to 127.0.0.1 only. It is the management interface, and it is intentionally not reachable from outside the VM.

5

Retrieve the Per-Instance Credentials

Every instance generates its own credentials on first boot. Nothing is shared between deployments, and no default password ships in the image.

sudo cat /stage/scripts/wildfly-credentials.log
# WildFly 40 - Per-VM Credentials WILDFLY_URL=http://10.0.0.23/ WILDFLY_CONSOLE_URL=http://127.0.0.1:9990/console WILDFLY_ADMIN_USER=admin WILDFLY_ADMIN_PASSWORD=<WILDFLY_ADMIN_PASSWORD> WILDFLY_APP_USER=appuser WILDFLY_APP_PASSWORD=<WILDFLY_APP_PASSWORD> HTTP_PORT=80 MGMT_PORT=9990 JBOSS_HOME=/opt/wildfly

The file lists exactly two accounts:

Account Realm Purpose

admin ManagementRealm Signs in to the management console and the management API

appuser ApplicationRealm Available to applications that use container managed security

6

Open the Application

nginx serves the application on port 80, so no tunnel is needed. Check it from the VM:

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

From your workstation, browse to http://<vm-ip>/.

7

Reach the Management Console

The management console binds to the loopback interface only. Open an SSH tunnel from your workstation:

ssh -L 9990:127.0.0.1:9990 azureuser@<vm-ip>

Leave that session open and browse to http://127.0.0.1:9990/console. Sign in as admin with the password from the credentials file.

The Runtime view reports live JVM metrics for the server, including heap use against the tuned 1024 MB maximum.

8

Query the Management API

The same operations are available over HTTP with digest authentication on the loopback interface:

PASS=$(sudo grep '^WILDFLY_ADMIN_PASSWORD=' /stage/scripts/wildfly-credentials.log | cut -d= -f2-)
curl -s --digest -u "admin:${PASS}" -H 'Content-Type: application/json' \
  -d '{"operation":"read-attribute","name":"product-version"}' \
  http://127.0.0.1:9990/management
curl -s --digest -u "admin:${PASS}" -H 'Content-Type: application/json' \
  -d '{"operation":"whoami"}' http://127.0.0.1:9990/management
curl -s --digest -u "admin:${PASS}" -H 'Content-Type: application/json' \
  -d '{"operation":"read-attribute","name":"server-state"}' \
  http://127.0.0.1:9990/management

Expected output:

{"outcome" : "success", "result" : "40.0.1.Final"} {"outcome" : "success", "result" : {"identity" : {"username" : "admin"}}} {"outcome" : "success", "result" : "running"}
9

Deploy Your First Application

WildFly watches /opt/wildfly/standalone/deployments/ and deploys any archive dropped into it. The block below builds a small Jakarta EE web application with jar from the bundled JDK and deploys it. It confirms that the application is being served, then removes it so the server is left exactly as it was.

WORK=$(mktemp -d); mkdir -p "$WORK/hello/WEB-INF" cat > "$WORK/hello/index.jsp" <<'JSP' <html><body> <h1>Hello from WildFly</h1> <p>Served by: <%= application.getServerInfo() %></p> </body></html> JSP cat > "$WORK/hello/WEB-INF/web.xml" <<'XML' <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.0"> <welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list> </web-app> XML jar -cf "$WORK/hello.war" -C "$WORK/hello" .
sudo install -o wildfly -g wildfly -m 0644 "$WORK/hello.war" /opt/wildfly/standalone/deployments/hello.war for i in $(seq 1 90); do
sudo test -f /opt/wildfly/standalone/deployments/hello.war.deployed && break sleep 1 done curl -s http://127.0.0.1/hello/
sudo rm -f /opt/wildfly/standalone/deployments/hello.war sleep 5
sudo rm -f /opt/wildfly/standalone/deployments/hello.war.* rm -rf "$WORK"

The second removal clears the .undeployed marker that the deployment scanner writes once it notices the archive is gone.

Expected output:

<html><body> <h1>Hello from WildFly</h1> <p>Served by: WildFly 40.0.1.Final (WildFly Core 32.0.2.Final) - 2.4.1.Final</p> </body></html>

While the archive is deployed, it appears in the console under Deployments.

The application renders in the browser, with the server information string produced by WildFly itself.

For production deployments, prefer the management API, which is transactional and reports failures. Substitute the path to your own archive for myapp.war:

PASS=$(sudo grep '^WILDFLY_ADMIN_PASSWORD=' /stage/scripts/wildfly-credentials.log | cut -d= -f2-)
curl --digest -u "admin:${PASS}" -F "file=@myapp.war" \
  http://127.0.0.1:9990/management-upload
10

Server Components

Component Path

JBOSS_HOME /opt/wildfly

Launch script /opt/wildfly/bin/standalone.sh

Active configuration /opt/wildfly/standalone/configuration/standalone-full.xml

Management realm users /opt/wildfly/standalone/configuration/mgmt-users.properties

Application realm users /opt/wildfly/standalone/configuration/application-users.properties

Deployments directory /opt/wildfly/standalone/deployments/

Server log /opt/wildfly/standalone/log/server.log

WildFly unit /etc/systemd/system/wildfly.service

First boot script /usr/local/sbin/wildfly-firstboot.sh

First boot sentinel /var/lib/sac/data/wildfly-firstboot.done

Bootstrap marker /var/lib/sac/data/wildfly-bootstrap-ready

Credentials file /stage/scripts/wildfly-credentials.log

nginx site /etc/nginx/sites-available/sac-wildfly

Confirm the Java runtime:

java -version

Expected output:

openjdk version "21.0.11" 2026-04-21 OpenJDK Runtime Environment (build 21.0.11+10-1-24.04.2-Ubuntu) OpenJDK 64-Bit Server VM (build 21.0.11+10-1-24.04.2-Ubuntu, mixed mode, sharing)
11

Managing the Services

sudo systemctl status wildfly.service --no-pager | head -5 sudo tail -n 20 /opt/wildfly/standalone/log/server.log

To restart or stop the server, use sudo systemctl restart wildfly.service and sudo systemctl stop wildfly.service. Manage nginx the same way, for example with sudo systemctl restart nginx.service.

12

Tuning the JVM Heap

The image ships with a heap sized for a 4 GB VM. On a larger VM, raise the heap by editing the JAVA_OPTS line in /etc/systemd/system/wildfly.service:

Environment="JAVA_OPTS=-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=512m"

Then run sudo systemctl daemon-reload, followed by sudo systemctl restart wildfly.service. As a rule of thumb, keep the maximum heap at or below half the VM memory, so the operating system and nginx keep plenty of headroom.

13

How the Security Model Works

This image is built so that no shared or default credential ever exists on a running instance.

  • No account ships in the image. WildFly's realm files are emptied before capture, and the build never creates a management user. There is no vendor default password to find.
  • The server cannot start unbootstrapped. wildfly.service carries a ConditionPathExists guard on /var/lib/sac/data/wildfly-bootstrap-ready. First boot writes that marker only after both realm passwords have been provisioned, so systemd does not start WildFly before it has its own credentials.
  • The management interface is not published. It binds to 127.0.0.1:9990 and is reached over an SSH tunnel.

You can verify all of this on your own instance:

curl -s -o /dev/null -w 'unauthenticated -> HTTP %{http_code}\n' \
  -H 'Content-Type: application/json' -d '{"operation":"whoami"}' \
  http://127.0.0.1:9990/management for P in admin:admin admin:password admin:wildfly jboss:jboss; do
C=$(curl -s -o /dev/null -w '%{http_code}' --digest -u "$P" \
  -H 'Content-Type: application/json' -d '{"operation":"whoami"}' \
  http://127.0.0.1:9990/management)
echo " $P -> HTTP $C" done

Expected output:

unauthenticated -> HTTP 401 admin:admin -> HTTP 401 admin:password -> HTTP 401 admin:wildfly -> HTTP 401 jboss:jboss -> HTTP 401
14

Security Recommendations

  • Rotate the admin password with sudo -u wildfly /opt/wildfly/bin/add-user.sh --silent --user admin --password '<new-password>', then restart the service.
  • Restrict the network security group so that port 80 is reachable only from networks you trust.
  • Terminate TLS in front of the VM using Azure Application Gateway, or add a certificate to the nginx site for direct HTTPS.
  • Keep the management interface on loopback. If you must reach it another way, use the SSH tunnel rather than rebinding it to a public address.
  • Apply operating system updates monthly. Unattended security upgrades are enabled by default.
15

Troubleshooting

The application returns 502 from nginx. WildFly is still starting or is not running. Check sudo systemctl status wildfly.service and sudo tail -n 50 /opt/wildfly/standalone/log/server.log.

WildFly is inactive and will not start. Confirm that the bootstrap marker exists with sudo test -f /var/lib/sac/data/wildfly-bootstrap-ready && echo present. If it is missing, first boot did not finish. Inspect it with sudo journalctl -u wildfly-firstboot.service --no-pager.

The credentials file contains only a comment. First boot has not completed yet. Wait for /var/lib/sac/data/wildfly-firstboot.done to appear, then read the file again.

The management console asks for a password repeatedly. The console uses HTTP digest authentication. Confirm that you are using the admin password from the credentials file on this specific VM, because it is unique per instance.

A deployment does not activate. Check for a .failed marker beside your archive in /opt/wildfly/standalone/deployments/ and read server.log for the deployment error.

16

Support and Licensing

WildFly is licensed under the Apache License 2.0, with no per-CPU or per-deployment fees. SecureAnyCloud provides commercial support for this image separately.

  • Email: sales@secureanycloud.com
  • Website: secureanycloud.com
  • Support hours: 24/7 with a 24-hour response SLA

Deploy on Azure

Launch WildFly 40 on Ubuntu 24.04 LTS with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure