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.shandjboss-cli.shboth work) wildfly.servicerunning as the unprivilegedwildflyuser with thestandalone-full.xmlprofile (web, EJB, JMS, JPA and web services)- An nginx reverse proxy serving the application on port 80
wildfly-firstboot.serviceprovisioning 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.
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.
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 StandardConnect 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.
Verify the Services
sudo systemctl status wildfly.service nginx.service --no-pagerBoth 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.
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/wildflyThe 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
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>/.
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.
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/managementExpected output:
{"outcome" : "success", "result" : "40.0.1.Final"} {"outcome" : "success", "result" : {"identity" : {"username" : "admin"}}} {"outcome" : "success", "result" : "running"}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-uploadServer 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 -versionExpected 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)Managing the Services
sudo systemctl status wildfly.service --no-pager | head -5 sudo tail -n 20 /opt/wildfly/standalone/log/server.logTo 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.
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.
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.servicecarries aConditionPathExistsguard 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:9990and 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" doneExpected output:
unauthenticated -> HTTP 401 admin:admin -> HTTP 401 admin:password -> HTTP 401 admin:wildfly -> HTTP 401 jboss:jboss -> HTTP 401Security 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.
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.
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