WebLogic with Bastion Host and ALB
Deploy WebLogic servers in private subnets behind a load balancer.
Introduction
30 Second Summary
Think about how large organizations keep their internal systems safe from direct public access while still serving thousands of users. Banks, hospitals, and e-commerce platforms all use layered network architectures where sensitive servers sit behind multiple security barriers.
In this project, you will build a production-style AWS infrastructure with private application servers, a secure jump server for maintenance access, and a load balancer that distributes traffic evenly across your servers.
What You'll Build
A multi-tier AWS network architecture hosting two Oracle WebLogic 14c application servers in private subnets, accessed securely through a bastion host and load-balanced via an Application Load Balancer.
By the end of this project, you'll have:
- A custom VPC with public and private subnets across two Availability Zones, secured by layered security groups.
- Two WebLogic application servers running in private subnets, accessible only through a bastion host in the public subnet.
- An Application Load Balancer distributing traffic via round-robin routing to both WebLogic servers, proving load distribution by alternating server responses.
- Secret Mission: Add a custom health check endpoint and test ALB failover behavior when a server goes down.
Are there any prerequisites?
You will need an AWS account with credits available. Basic familiarity with VPC and EC2 concepts is helpful, along with access to an SSH client on your local machine.
Set Up the VPC and Network Infrastructure
Every enterprise application needs a secure network foundation. In this project, your WebLogic servers will live in private subnets that are completely isolated from direct internet access.
To reach those private servers, you will build a layered network with a VPC, public and private subnets, a NAT Gateway, and carefully scoped security groups. This mirrors how production teams separate web-facing infrastructure from backend application servers.
In this step, get ready to:
- Create a VPC with public and private subnets across two Availability Zones.
- Configure three security groups to control traffic between layers.
- Verify that route tables correctly direct traffic through the Internet Gateway and NAT Gateway.
Create the VPC using the "VPC and more" wizard
The AWS VPC Console offers a wizard that creates your VPC, subnets, route tables, Internet Gateway, and NAT Gateway all at once. This saves you from creating each resource individually.
- In the AWS Management Console, search for VPC in the top search bar and select the VPC service.
- On the VPC Dashboard, click Create VPC.
- For Resources to create, select VPC and more.
- For Name tag auto-generation, enter weblogic. This prefixes all created resources with "weblogic" so you can easily identify them.
- For IPv4 CIDR block, enter 10.0.0.0/16.
- For Number of Availability Zones, choose 2.
- For Number of public subnets, choose 2.
- For Number of private subnets, choose 2.
- Expand Customize subnet CIDR blocks. Set the public subnets to 10.0.1.0/24 and 10.0.2.0/24. Set the private subnets to 10.0.3.0/24 and 10.0.4.0/24.
- For NAT gateways, choose In 1 AZ. This keeps costs low while still giving private instances internet access.
- For VPC endpoints, choose None.
- Review the Preview pane on the right to see how your resources connect. Solid lines show relationships between resources.
- Click Create VPC.
The wizard takes a moment to create all resources. Wait until you see a green success banner.
What did the wizard just create?
The "VPC and more" wizard created your entire network foundation in one step. It built the VPC itself, two public subnets and two private subnets spread across two Availability Zones, an Internet Gateway for public internet access, a NAT Gateway in one of the public subnets, and route tables wiring everything together.
Public subnets route outbound traffic to the Internet Gateway. Private subnets route outbound traffic to the NAT Gateway, which lets them reach the internet without being directly reachable from it.
💡 What is a NAT Gateway?
A NAT Gateway allows instances in private subnets to connect to the internet but prevents the internet from initiating connections with those instances. Your WebLogic servers need this to download packages and updates, while staying hidden from direct external access.
- Navigate to Your VPCs in the left sidebar.
- Confirm that your VPC named weblogic-vpc shows a status of Available.
VPC creation failed?
If you see an error about Elastic IP limits, you may have hit your account's EIP quota. Navigate to Service Quotas in the console and request an increase for EC2 Elastic IPs. Why did my VPC creation fail with an Elastic IP error?
If you see an error about VPC limits, check whether you already have 5 VPCs in the selected region (the default quota). Delete unused VPCs or switch to a different region.
Create three security groups
Security groups act as virtual firewalls controlling which traffic can reach your instances. You need three groups that work together in layers: one for the bastion host, one for the load balancer, and one for the WebLogic servers.
The order matters here. You will create the Bastion SG and ALB SG first, because the WebLogic SG references both of them in its inbound rules.
- In the VPC Console left sidebar, click Security groups.
- Click Create security group.
Start with the Bastion security group:
- For Security group name, enter Bastion-SG.
- For Description, enter Allow SSH from my IP only.
- For VPC, select your weblogic-vpc.
- Under Inbound rules, click Add rule.
- Set Type to SSH (this auto-fills port 22).
- Set Source to My IP. This automatically detects your current public IP address and restricts SSH access to only you.
- Click Create security group.
Next, create the ALB security group:
- Click Create security group again.
- For Security group name, enter ALB-SG.
- For Description, enter Allow HTTP from anywhere.
- For VPC, select your weblogic-vpc.
- Under Inbound rules, click Add rule.
- Set Type to HTTP (this auto-fills port 80).
- Set Source to Anywhere-IPv4 (0.0.0.0/0). The Application Load Balancer needs to accept traffic from any user on the internet.
- Click Create security group.
Finally, create the WebLogic security group:
- Click Create security group again.
- For Security group name, enter WebLogic-SG.
- For Description, enter Allow SSH from Bastion and HTTP 7001 from ALB.
- For VPC, select your weblogic-vpc.
- Under Inbound rules, click Add rule.
- Set Type to SSH (port 22).
- Set Source to Custom, then search for and select the Bastion-SG you just created. This means only instances attached to the Bastion SG can SSH into the WebLogic servers.
- Click Add rule to add a second rule.
- Set Type to Custom TCP.
- Set Port range to 7001. This is the default port for WebLogic Server.
- Set Source to Custom, then search for and select the ALB-SG you created. This ensures only the load balancer can reach WebLogic on port 7001.
- Click Create security group.
Why reference security groups instead of IP addresses?
When you set the source of a rule to another security group, AWS automatically allows traffic from any instance attached to that group. This is more flexible than hardcoding IP addresses, because instances can be replaced or scaled without updating firewall rules.
In this setup, the WebLogic SG only accepts SSH from the bastion and HTTP from the ALB. Even if an attacker reaches the public subnet, they cannot directly connect to your application servers.
🙋♀️ Can't find your VPC when creating a security group?
Make sure you are in the same region where you created the VPC. Check the region selector in the top-right corner of the console. If the VPC was created in us-east-1, switch to that region. Help me find my VPC when creating security groups.
Verify the route tables
The VPC wizard automatically created route tables, but it is important to confirm they are wired correctly. Public subnets need a route to the Internet Gateway. Private subnets need a route to the NAT Gateway.
- In the VPC Console left sidebar, click Route tables.
- Find the route table associated with your public subnets (it will have "public" in the name if auto-tagged by the wizard).
- Click on the route table ID to open its details.
- Select the Routes tab. Confirm you see a route with Destination set to 0.0.0.0/0 and Target pointing to an Internet Gateway (igw- prefix).
- Go back to Route tables and find the route table associated with your private subnets (it will have "private" in the name).
- Select the Routes tab. Confirm you see a route with Destination set to 0.0.0.0/0 and Target pointing to a NAT Gateway (nat- prefix).
What do these routes mean?
The 0.0.0.0/0 destination is a catch-all for "all traffic not destined for the local VPC." In public subnets, this traffic goes directly to the Internet Gateway. In private subnets, this traffic goes to the NAT Gateway, which forwards it to the internet on behalf of the private instance.
This is what makes a subnet "public" or "private." The subnet type is determined entirely by its routing, not by any label.
- Optionally, click Subnet associations on each route table to confirm the correct subnets are attached.
Missing route to NAT Gateway?
If the private route table only shows a local route and no 0.0.0.0/0 entry, the NAT Gateway may still be provisioning. Navigate to NAT gateways in the sidebar and wait until the status changes from "Pending" to "Available." If the NAT Gateway failed, you may need to delete the VPC and re-run the wizard. Help me troubleshoot my missing NAT Gateway route.
Your network foundation is ready. You have a VPC with isolated public and private subnets, layered security groups, and properly configured routing. Next up, you will launch the bastion host and WebLogic instances into this infrastructure.
Launch the Bastion Host and WebLogic Instances
Your network is in place. The VPC, subnets, and security groups you created in the previous step provide the isolation and access rules. Now you need the actual servers.
In this step, you will launch three EC2 instances: one bastion host in the public subnet and two WebLogic application servers in the private subnets. You will then verify connectivity by using SSH agent forwarding to hop from the bastion into each private instance.
In this step, get ready to:
- Launch a bastion host in the public subnet with a public IP.
- Launch two WebLogic instances in private subnets without public IPs.
- Test SSH connectivity from your local machine through the bastion to both private instances.
Launch the bastion host
The bastion host is a small instance in the public subnet that acts as a secure entry point. You will SSH into it first, then hop to the private servers from there.
- In the AWS Management Console, navigate to the EC2 service.
- Click Launch instance from the EC2 dashboard.
- Under Name and tags, enter Bastion-Host in the Name field.
- Under Application and OS Images (Amazon Machine Image), choose Quick Start.
- Select Amazon Linux and confirm the Amazon Linux 2023 AMI is selected.
- Under Instance type, select t3.micro.
- Under Key pair (login), select your existing key pair.
- Under Network settings, click Edit.
- For VPC, select your weblogic-vpc.
- For Subnet, select Public Subnet 1 (10.0.1.0/24).
- For Auto-assign public IP, select Enable.
- For Firewall (security groups), choose Select existing security group.
- Select your Bastion SG from the dropdown.
- In the Summary panel, click Launch instance.
What is a bastion host?
A bastion host is a server that provides a single point of access from an external network to the resources located in a private network. It sits in the public subnet so you can SSH to it from the internet. From there, you jump to private instances that have no public IP.
This pattern keeps your application servers hidden from direct internet access while still allowing administrators to reach them for management.
- Wait for the instance state to change from Pending to Running on the Instances page.
- Note the Public IPv4 address displayed for your Bastion-Host instance. You will need this to SSH in shortly.
Instance stuck in Pending?
If the instance stays in Pending for more than 2 minutes, check that you selected a valid subnet within your weblogic-vpc and that you chose a supported instance type for the selected region.
Help me troubleshoot this
Launch the two WebLogic instances
The WebLogic servers need more memory than the bastion, so they use t3.medium instances (2 vCPUs, 4 GiB RAM). They sit in private subnets with no public IP, accessible only through the bastion.
You will repeat the launch process twice, placing one server in each private subnet.
- Return to the EC2 dashboard and click Launch instance.
- Under Name and tags, enter WebLogic-Server-1 in the Name field.
- Under Application and OS Images, choose Quick Start and select the Amazon Linux 2023 AMI.
- Under Instance type, select t3.medium.
- Under Key pair (login), select the same key pair you used for the bastion.
- Under Network settings, click Edit.
- For VPC, select weblogic-vpc.
- For Subnet, select Private Subnet 1 (10.0.3.0/24).
- For Auto-assign public IP, select Disable.
- For Firewall (security groups), choose Select existing security group and select your WebLogic SG.
- Click Launch instance.
Now repeat the exact same process for the second server.
- Click Launch instance again from the EC2 dashboard.
- Under Name and tags, enter WebLogic-Server-2.
- Choose the Amazon Linux 2023 AMI and t3.medium instance type.
- Select the same key pair.
- Under Network settings, click Edit and select weblogic-vpc.
- For Subnet, select Private Subnet 2 (10.0.4.0/24).
- For Auto-assign public IP, select Disable.
- Select your WebLogic SG security group.
- Click Launch instance.
- Return to the Instances page and wait for all three instances to show Running state.
- Note the Private IPv4 address for both WebLogic-Server-1 and WebLogic-Server-2. You will need these to SSH from the bastion.
Why the same key pair for all instances?
Using the same key pair across all three instances lets SSH agent forwarding work. Your local SSH agent holds the private key and forwards it through the bastion to authenticate against the private instances. No private key needs to be stored on the bastion itself.
🙋♀️ Don't see your private instances?
Confirm you selected weblogic-vpc in the Network settings. If you accidentally used the default VPC, the instances will launch in the wrong network and your security group rules will not apply.
Help me troubleshoot my instances
Test bastion connectivity with SSH agent forwarding
SSH agent forwarding lets you authenticate to private instances through the bastion without copying your private key onto the bastion. Your local machine holds the key, and the SSH agent forwards the credentials through the connection.
- Open a terminal on your local machine.
- Add your key to the SSH agent by running this command:
ssh-add [[KEYPAIR_PATH="path/to/your-key.pem"]]
What does ssh-add do?
The ssh-add command loads your private key into the SSH authentication agent running on your local machine. Once loaded, the agent can forward this key through SSH connections without exposing the file itself.
- Connect to the bastion host with agent forwarding enabled by running this command:
ssh -A -i [[KEYPAIR_PATH="path/to/your-key.pem"]] ec2-user@[[BASTION_PUBLIC_IP="your bastion public IPv4 address"]]
What do these flags mean?
- -A enables agent forwarding, which lets the bastion use your local key to authenticate onward connections.
- -i specifies which private key file to use for this connection.
- ec2-user is the default username for Amazon Linux 2023 instances.
You should see a terminal prompt that looks like [ec2-user@ip-10-0-1-xxx ~]$ confirming you are on the bastion.
- From the bastion, SSH into WebLogic-Server-1 using its private IP:
ssh ec2-user@[[WL_SERVER_1_PRIVATE_IP="WebLogic-Server-1 private IPv4 address"]]
- Confirm the prompt changes to something like [ec2-user@ip-10-0-3-xxx ~]$.
- Test internet connectivity through the NAT Gateway by running:
ping -c 3 google.com
You should see three successful ping replies confirming the private instance can reach the internet via the NAT Gateway.
- Type exit to return to the bastion.
- From the bastion, SSH into WebLogic-Server-2 using its private IP:
ssh ec2-user@[[WL_SERVER_2_PRIVATE_IP="WebLogic-Server-2 private IPv4 address"]]
- Confirm you see a prompt like [ec2-user@ip-10-0-4-xxx ~]$.
- Run the ping test again to verify internet access:
ping -c 3 google.com
You should see three successful replies here as well.
- Type exit to return to the bastion. Leave the bastion session open for the next step.
Connection timed out to private instance?
If SSH to the private instance hangs or times out, check these items:
- Confirm the WebLogic SG allows inbound SSH (port 22) from the Bastion SG as the source.
- Verify you used the private IP of the WebLogic instance, not the subnet CIDR.
- Ensure both instances use the same key pair. Agent forwarding only works if the key loaded into your local agent matches what the private instance expects.
Help me fix my SSH connection
🙋♀️ Ping to google.com fails from private instance?
If ping returns an error or times out, your NAT Gateway routing may be misconfigured. Check that the private route table routes 0.0.0.0/0 to the NAT Gateway, and that the NAT Gateway itself is in a public subnet with a route to the Internet Gateway.
Help me fix my NAT Gateway setup
All three instances are running and you have confirmed the connectivity chain works. Next up, you will install Oracle WebLogic Server on both private instances through this bastion connection.
Install WebLogic on Both Private Servers
Your bastion host is up, and you can SSH into both private WebLogic instances. Now it is time to install the actual application server software.
In this step, you will install Amazon Corretto 11 (the JDK that WebLogic requires), transfer and run the WebLogic 14c installer, create a domain with WLST, and deploy a simple JSP app that identifies which server is responding.
In this step, get ready to:
- Install Amazon Corretto 11 JDK on both private instances.
- Transfer and install WebLogic 14c on both servers.
- Create a WebLogic domain and deploy a JSP app on each server.
Install Amazon Corretto 11 JDK on each instance
WebLogic 14c (14.1.1.0) requires JDK 11. Amazon Corretto 11 is an OpenJDK distribution that satisfies this requirement on Amazon Linux 2.
- SSH into the bastion host from your local machine using SSH agent forwarding:
ssh -A -i your-key.pem ec2-user@[[BASTION_IP="your bastion public IP"]]
- From the bastion, SSH into WebLogic-Server-1 using its private IP:
ssh ec2-user@[[WLS1_PRIVATE_IP="WebLogic-Server-1 private IP"]]
- Install Amazon Corretto 17 by running this command:
sudo yum install java-11-amazon-corretto-devel -y
- Verify the installation by running:
java -version
What should I see?
You should see output similar to:
openjdk version "11.0.25" 2024-10-15 LTS
OpenJDK Runtime Environment Corretto-11.0.25.9.1 (build 11.0.25+9-LTS)
The exact patch version may vary, but it should show Corretto 11.
- Set the JAVA_HOME environment variable, create the Oracle directories, and install the Oracle OpenJDK 11 RPM by running these commands:
# Set JAVA_HOME
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Create Oracle directories
sudo mkdir -p /home/ec2-user/oracle/wls14
sudo mkdir -p /home/ec2-user/oraInventory
sudo chown -R ec2-user:ec2-user /home/ec2-user/oracle
sudo chown -R ec2-user:ec2-user /home/ec2-user/oraInventory
# Download and install Oracle OpenJDK 11
sudo rpm -ivh jdk-11.0.*_linux-x64_bin.rpm
Why install Oracle OpenJDK via RPM?
WebLogic 14c requires specific Oracle JDK libraries that Amazon Corretto alone does not provide. Installing the Oracle OpenJDK 11 RPM ensures WebLogic's silent installer and runtime tools find all the dependencies they need.
💡 What does this do?
The JAVA_HOME variable tells WebLogic where to find the JDK. Adding it to ~/.bashrc means it will be available every time you log in to the instance.
The Oracle directories (/home/ec2-user/oracle/wls14 and /home/ec2-user/oraInventory) are required by the WebLogic installer to store binaries and inventory metadata. The Oracle OpenJDK 11 RPM provides additional libraries that WebLogic needs beyond what Corretto supplies.
- Exit back to the bastion by running:
exit
- Repeat the same process on WebLogic-Server-2. SSH into it from the bastion:
ssh ec2-user@[[WLS2_PRIVATE_IP="WebLogic-Server-2 private IP"]]
- Run the same installation and configuration commands on Server 2:
# Install Corretto 11
sudo yum install -y java-11-amazon-corretto-devel
# Verify
java -version
# Set JAVA_HOME
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Create Oracle directories
sudo mkdir -p /home/ec2-user/oracle/wls14
sudo mkdir -p /home/ec2-user/oraInventory
sudo chown -R ec2-user:ec2-user /home/ec2-user/oracle
sudo chown -R ec2-user:ec2-user /home/ec2-user/oraInventory
# Download and install Oracle OpenJDK 11
sudo rpm -ivh jdk-11.0.*_linux-x64_bin.rpm
- Exit back to the bastion:
exit
Java install failing?
Make sure your private instances can reach the internet via the NAT Gateway. Test with ping -c 3 google.com from the private instance. If it times out, check that the private subnet route table routes 0.0.0.0/0 to the NAT Gateway.
help me debug why my private EC2 instance cannot reach the internet through the NAT Gateway
Transfer and install the WebLogic 14c installer
The WebLogic installer JAR needs to get from your local machine to the private instances. Since the private instances have no public IP, you will transfer the file through the bastion host.
- On your local machine (not the bastion), open a new terminal session.
- First, unzip the downloaded WebLogic installer ZIP to extract the JAR file. The file inside is named fmw_14.1.1.0.0_wls_lite_generic.jar.
- Copy the installer JAR to the bastion host by running:
scp -i your-key.pem fmw_14.1.1.0.0_wls_lite_generic.jar ec2-user@[[BASTION_IP="your bastion public IP"]]:/home/ec2-user/
- SSH back into the bastion (with agent forwarding):
ssh -A -i your-key.pem ec2-user@[[BASTION_IP="your bastion public IP"]]
- From the bastion, copy the installer to WebLogic-Server-1:
scp fmw_14.1.1.0.0_wls_lite_generic.jar ec2-user@[[WLS1_PRIVATE_IP="WebLogic-Server-1 private IP"]]:/home/ec2-user/
- Copy the installer to WebLogic-Server-2:
scp fmw_14.1.1.0.0_wls_lite_generic.jar ec2-user@[[WLS2_PRIVATE_IP="WebLogic-Server-2 private IP"]]:/home/ec2-user/
- SSH into WebLogic-Server-1 from the bastion:
ssh ec2-user@[[WLS1_PRIVATE_IP="WebLogic-Server-1 private IP"]]
- Run the WebLogic silent installer by running this command:
# Install WebLogic in silent mode to the specified ORACLE_HOME
cat > /home/ec2-user/oraInst.loc << 'EOF'
inventory_loc=/home/ec2-user/oraInventory
inst_group=ec2-user
EOF
cat > /home/ec2-user/wls.rsp << 'EOF'
[ENGINE]
Response File Version=1.0.0.0.0
[GENERIC]
ORACLE_HOME=/home/ec2-user/oracle/wls14
INSTALL_TYPE=WebLogic Server
DECLINE_SECURITY_UPDATES=true
SECURITY_UPDATES_VIA_MYORACLESUPPORT=false
EOF
java -jar fmw_14.1.1.0.0_wls_lite_generic.jar -silent -responseFile /home/ec2-user/wls.rsp -invPtrLoc /home/ec2-user/oraInst.loc -ignoreSysPrereqs
What does this command do?
The java -jar command runs the WebLogic installer in silent mode. The ORACLE_HOME parameter tells the installer where to place all WebLogic binaries. This single command installs the entire WebLogic Server runtime.
The installation takes 1-2 minutes. You will see progress messages as it extracts and installs the files.
- Verify the installation by checking the WLST script exists:
ls /home/ec2-user/oracle/wls14/oracle_common/common/bin/wlst.sh
You should see the file path echoed back, confirming WebLogic installed successfully.
- Exit back to the bastion and repeat on WebLogic-Server-2:
exit
ssh ec2-user@[[WLS2_PRIVATE_IP="WebLogic-Server-2 private IP"]]
- Run the same installer on Server 2:
# Install WebLogic in silent mode to the specified ORACLE_HOME
cat > /home/ec2-user/oraInst.loc << 'EOF'
inventory_loc=/home/ec2-user/oraInventory
inst_group=ec2-user
EOF
cat > /home/ec2-user/wls.rsp << 'EOF'
[ENGINE]
Response File Version=1.0.0.0.0
[GENERIC]
ORACLE_HOME=/home/ec2-user/oracle/wls14
INSTALL_TYPE=WebLogic Server
DECLINE_SECURITY_UPDATES=true
SECURITY_UPDATES_VIA_MYORACLESUPPORT=false
EOF
java -jar fmw_14.1.1.0.0_wls_lite_generic.jar -silent -responseFile /home/ec2-user/wls.rsp -invPtrLoc /home/ec2-user/oraInst.loc -ignoreSysPrereqs
- Verify the installation on Server 2:
ls /home/ec2-user/oracle/wls14/oracle_common/common/bin/wlst.sh
Installer failing with memory errors?
The t3.medium instances have 4 GiB of RAM. If the installer reports insufficient memory, check that no other large processes are running with free -m. The installer requires at least 800MB of free disk space.
help me troubleshoot WebLogic installer errors on an Amazon Linux 2 EC2 instance
Create a WebLogic domain and deploy the JSP application
A WebLogic domain is a logical grouping of server instances and resources. You will use WLST (WebLogic Scripting Tool) to create a development domain offline, then deploy a simple JSP page that displays the server hostname.
What is WLST?
WLST is a Jython-based command-line scripting tool for creating and managing WebLogic domains. In offline mode, it can create a new domain from a template without needing a running server. This is how enterprise teams automate domain provisioning.
- On WebLogic-Server-1 (you should still be connected via the bastion), create a WLST domain creation script by running:
cat > /home/ec2-user/create_domain.py << 'EOF'
# WLST offline script to create a WebLogic development domain
# Load the base WebLogic Server domain template
readTemplate('/home/ec2-user/oracle/wls14/wlserver/common/templates/wls/wls.jar')
# Configure the Admin Server to listen on port 7001
cd('Servers/AdminServer')
set('ListenAddress', '')
set('ListenPort', 7001)
# Set the admin credentials
cd('/')
cd('Security/base_domain/User/weblogic')
cmo.setPassword('welcome1')
# Write the domain to disk
setOption('OverwriteDomain', 'true')
writeDomain('/home/ec2-user/oracle/wls14/user_projects/domains/base_domain')
closeTemplate()
print('Domain created successfully!')
EOF
What does this script do?
- readTemplate loads the basic WebLogic Server domain template (wls.jar).
- The script configures the Admin Server to listen on port 7001 with an empty listen address (accepts connections on all interfaces).
- It sets the default admin password to welcome1 for this development environment.
- writeDomain saves the domain configuration to the filesystem.
- Run the WLST script to create the domain:
/home/ec2-user/oracle/wls14/oracle_common/common/bin/wlst.sh /home/ec2-user/create_domain.py
You should see WLST initialize and then print "Domain created successfully!" at the end.
- Create the web application directory structure for deployment. WebLogic auto-deploys applications placed in the domain's autodeploy folder when running in development mode. Create the app directory by running:
# Create an exploded web app directory named 'app' in autodeploy
mkdir -p /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/WEB-INF
- Create the web.xml deployment descriptor:
cat > /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/WEB-INF/web.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
version="6.0">
<display-name>Hostname App</display-name>
</web-app>
EOF
- Create the index.jsp file that displays the server hostname:
cat > /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/index.jsp << 'EOF'
<%@ page contentType="text/html;charset=UTF-8" %>
<%
// Get the server hostname to identify which instance is responding
String hostname = java.net.InetAddress.getLocalHost().getHostName();
%>
Response from: <%= hostname %>
EOF
What does index.jsp do?
This JSP page calls InetAddress.getLocalHost().getHostName() to get the private hostname of the EC2 instance. The hostname on AWS private instances follows the pattern ip-10-0-3-xxx, making it easy to identify which server responded to a request. The Application Load Balancer will use this to prove round-robin distribution later.
- Start the WebLogic Admin Server in the background by running:
# Start WebLogic in the background with limited heap to fit t3.medium memory
cd /home/ec2-user/oracle/wls14/user_projects/domains/base_domain
nohup ./bin/startWebLogic.sh > /home/ec2-user/weblogic.log 2>&1 &
Why nohup?
The nohup command keeps WebLogic running even after you close the SSH session. The output goes to weblogic.log so you can check it later if needed. The server takes about 30-60 seconds to fully start.
- Wait about 60 seconds for the server to start, then verify it is running:
curl http://localhost:7001/app/
You should see output like:
Response from: ip-10-0-3-xxx
The exact IP portion will match your Server 1 private address.
Curl returning connection refused?
WebLogic may still be starting. Check the log with tail -f /home/ec2-user/weblogic.log and wait until you see a message indicating the server is in RUNNING state. This can take up to 90 seconds on a t3.medium instance.
If you see Java heap errors, set memory limits before starting by exporting USER_MEM_ARGS="-Xms512m -Xmx1g" before running startWebLogic.sh.
help me troubleshoot WebLogic Admin Server not starting on my EC2 private instance
- Now repeat the entire domain creation and deployment process on WebLogic-Server-2. Exit back to the bastion:
exit
- SSH into WebLogic-Server-2:
ssh ec2-user@[[WLS2_PRIVATE_IP="WebLogic-Server-2 private IP"]]
- Create the domain script, run WLST, deploy the app, and start WebLogic on Server 2 by running these commands:
# Create the WLST domain creation script
cat > /home/ec2-user/create_domain.py << 'EOF'
readTemplate('/home/ec2-user/oracle/wls14/wlserver/common/templates/wls/wls.jar')
cd('Servers/AdminServer')
set('ListenAddress', '')
set('ListenPort', 7001)
cd('/')
cd('Security/base_domain/User/weblogic')
cmo.setPassword('welcome1')
setOption('OverwriteDomain', 'true')
writeDomain('/home/ec2-user/oracle/wls14/user_projects/domains/base_domain')
closeTemplate()
print('Domain created successfully!')
EOF
# Run WLST to create the domain
/home/ec2-user/oracle/wls14/oracle_common/common/bin/wlst.sh /home/ec2-user/create_domain.py
# Create the web app directory structure
mkdir -p /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/WEB-INF
# Create web.xml
cat > /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/WEB-INF/web.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
version="6.0">
<display-name>Hostname App</display-name>
</web-app>
EOF
# Create index.jsp
cat > /home/ec2-user/oracle/wls14/user_projects/domains/base_domain/autodeploy/app/index.jsp << 'EOF'
<%@ page contentType="text/html;charset=UTF-8" %>
<%
String hostname = java.net.InetAddress.getLocalHost().getHostName();
%>
Response from: <%= hostname %>
EOF
# Start WebLogic in the background
cd /home/ec2-user/oracle/wls14/user_projects/domains/base_domain
nohup ./bin/startWebLogic.sh > /home/ec2-user/weblogic.log 2>&1 &
- Wait about 60 seconds, then verify:
curl http://localhost:7001/app/
You should see output like:
Response from: ip-10-0-4-xxx
The IP portion matches your Server 2 private address, confirming both servers are running independently.
Both WebLogic servers are running and serving responses that identify themselves. Next, you will put an Application Load Balancer in front of them to distribute traffic automatically.
Configure the Application Load Balancer
Your two WebLogic servers are running and serving the hostname JSP app. But they're tucked away in private subnets with no public IP addresses, which means external users can't reach them directly.
An Application Load Balancer solves this by sitting in the public subnets and distributing incoming HTTP requests across your private servers. In this step, you'll create a target group to define where traffic goes, then wire up the ALB to accept requests from the internet and forward them to both WebLogic instances.
In this step, get ready to:
- Create a target group pointing at both WebLogic servers on port 7001.
- Create an internet-facing Application Load Balancer with an HTTP listener on port 80.
- Verify both targets are healthy and note the ALB DNS name.
Create the target group
A target group tells the ALB which instances to forward traffic to and how to check if they're healthy. You'll point it at port 7001, which is where WebLogic serves your application.
- In the AWS Management Console, open the EC2 service.
- In the left navigation pane, scroll down to the Load Balancing section.
- Click Target Groups.
- Click Create target group.
- For Choose a target type, select Instances.
- For Target group name, enter weblogic-tg.
- Set Protocol to HTTP and Port to 7001.
- For VPC, select weblogic-vpc.
- Expand the Health checks section.
- Change the Health check path from / to /app/.
- Click Next.
The next page lets you register targets. This is where you add both WebLogic instances.
- In the Available instances list, select the checkboxes next to WebLogic-Server-1 and WebLogic-Server-2.
- Confirm the port shows 7001.
- Click Include as pending below.
- Verify both instances appear in the Review targets table at the bottom.
- Click Create target group.
What is a target group?
A target group is a collection of resources (in this case, EC2 instances) that receive traffic from a load balancer. The ALB sends periodic health check requests to each target at the path you specified. If a target stops responding, the ALB stops routing traffic to it.
🙋♀️ Don't see your instances in the list?
Make sure you selected weblogic-vpc as the VPC on the previous page. Instances in a different VPC won't appear. Go back and check the VPC selection.
help me find my WebLogic instances in the target group registration page
Create the Application Load Balancer
Now you'll create the ALB itself. It sits in your public subnets and listens for HTTP traffic on port 80, forwarding requests to the target group you just created.
- In the left navigation pane under Load Balancing, click Load Balancers.
- Click Create load balancer.
- Under Application Load Balancer, click Create.
- For Load balancer name, enter weblogic-alb.
- For Scheme, select Internet-facing.
- For IP address type, keep IPv4 selected.
Next, configure the network mapping to place the ALB in both public subnets.
- In the Network mapping section, select weblogic-vpc for the VPC.
- Under Mappings, check the boxes for us-east-1a and us-east-1b.
- For each Availability Zone, select the corresponding public subnet (10.0.1.0/24 for us-east-1a and 10.0.2.0/24 for us-east-1b).
Now configure the security group and listener.
- In the Security groups section, remove the default security group if pre-selected.
- Select your ALB SG (the one allowing inbound HTTP on port 80 from 0.0.0.0/0).
- In the Listeners and routing section, confirm the listener is set to HTTP on port 80.
- For Default action, select the weblogic-tg target group from the dropdown.
- Scroll down and click Create load balancer.
Why internet-facing?
An internet-facing ALB gets a public DNS name that anyone on the internet can reach. It sits in the public subnets (which have routes to the Internet Gateway) and forwards traffic to targets in the private subnets. This is the bridge that lets external users reach your private WebLogic servers without exposing them directly.
🙋♀️ Don't see your target group in the dropdown?
Make sure you selected weblogic-vpc in the Network mapping section. The target group only appears if the ALB's VPC matches the target group's VPC.
help me connect my ALB to my target group
Verify health checks and note the ALB DNS name
The ALB needs to confirm your WebLogic servers are responding before it sends them traffic. Health checks may take 30 to 60 seconds to complete.
- In the left navigation pane, click Target Groups.
- Click weblogic-tg to open it.
- Click the Targets tab.
- Wait for both instances to show a status of healthy. You may need to click the refresh icon a few times over 30-60 seconds.
Both targets should show healthy status. This confirms the ALB can reach your WebLogic servers on port 7001 at the /app/ path.
- In the left navigation pane, click Load Balancers.
- Select weblogic-alb.
- In the Description section, find the DNS name field (it looks something like weblogic-alb-123456789.us-east-1.elb.amazonaws.com).
- Copy this DNS name and save it somewhere. You'll use it in the next step to test load balancing.
Why is round-robin the default?
The ALB uses the round_robin routing algorithm by default. This means it distributes requests evenly across all healthy targets in sequential order. You don't need to configure anything special to get this behavior.
AWS also offers least_outstanding_requests and weighted_random as alternative algorithms, but round-robin is the simplest starting point for even distribution.
🙋♀️ Targets stuck in initial or unhealthy status?
Check that your WebLogic servers are still running. SSH into each instance via the bastion and run curl http://localhost:7001/app/ to confirm the app responds.
Also verify that the WebLogic SG allows inbound traffic on port 7001 from the ALB SG. If the security group rule is missing, the health checks will fail.
help me fix unhealthy targets in my ALB target group
Your load balancer is live and ready to distribute traffic. Next up, you'll hit that DNS name in a browser and watch the responses alternate between your two private WebLogic servers.
Test Round-Robin Load Balancing
Your infrastructure is fully wired up. The ALB is listening on port 80 and both WebLogic servers are registered as healthy targets.
Now comes the payoff. You will hit the ALB's public DNS name and watch traffic distribute across both private servers. This proves the entire architecture works end-to-end, from the internet through the ALB down into your isolated private subnets.
In this step, get ready to:
- Test the ALB in a browser and observe hostname responses alternating between servers.
- Verify round-robin distribution from the command line using a curl loop.
Access the ALB in your browser
The Application Load Balancer has a public DNS name that anyone on the internet can reach. When a request arrives, the ALB forwards it to one of the healthy targets in its target group using the round_robin algorithm.
- Return to the EC2 console and select Load Balancers in the left sidebar.
- Select your weblogic-alb load balancer.
- Copy the DNS name value from the Description section (it looks like weblogic-alb-123456789.us-east-1.elb.amazonaws.com).
- Open a new browser tab and navigate to:
http://[[ALB_DNS="your-alb-dns-name.us-east-1.elb.amazonaws.com"]]/app/
You should see a response like Response from: ip-10-0-3-xxx showing one of your WebLogic server's hostnames.
- Refresh the page several times by pressing Cmd+R (macOS) or Ctrl+R (Windows).
- Watch the hostname change between responses. You should see it alternate between your two servers (for example, ip-10-0-3-xxx and ip-10-0-4-xxx).
Why might I see the same server twice in a row?
The ALB uses multiple internal nodes across Availability Zones. Occasionally, consecutive requests land on the same ALB node, which routes to the same target before synchronizing. Over several refreshes, you will see both servers respond.
🙋♀️ Seeing a timeout or error?
Make sure you are using http:// (not https://). Your ALB listener is configured for HTTP on port 80 only.
Check that both targets show as healthy in the target group. If a target is unhealthy, the ALB will not route traffic to it.
Confirm the ALB security group allows inbound HTTP on port 80 from 0.0.0.0/0.
Help me troubleshoot why my ALB is not responding
Verify round-robin with a curl loop
Refreshing a browser is a good visual check, but a scripted loop gives you definitive proof. Running ten requests in quick succession clearly shows both servers responding in alternation.
- Open your local terminal (the same one you used to SSH into the bastion earlier).
- Run the following curl loop to send 10 requests to the ALB:
for i in $(seq 1 10); do curl -s http://[[ALB_DNS="your-alb-dns-name.us-east-1.elb.amazonaws.com"]]/app/; echo ""; done
What does this command do?
The for loop runs curl -s (silent mode) ten times against your ALB URL. Each iteration fetches the page and prints the response body on its own line. The echo "" adds a newline between each response for readability.
Your output should look something like this, with hostnames alternating between the two private subnets:
Response from: ip-10-0-3-xxx
Response from: ip-10-0-4-xxx
Response from: ip-10-0-3-xxx
Response from: ip-10-0-4-xxx
Response from: ip-10-0-3-xxx
Response from: ip-10-0-4-xxx
Response from: ip-10-0-3-xxx
Response from: ip-10-0-4-xxx
Response from: ip-10-0-3-xxx
Response from: ip-10-0-4-xxx
Both hostnames appearing confirms the ALB is distributing requests across both private WebLogic instances using round-robin.
What is round-robin routing?
Round-robin routes requests evenly across healthy targets in the target group, in a sequential order. The ALB uses round_robin as its default routing algorithm. No additional configuration was needed to enable this.
The other available algorithms are least_outstanding_requests and weighted_random. For this project, the default is exactly what you want: equal distribution across both servers.
🙋♀️ Only seeing one server in the output?
If all 10 responses show the same hostname, wait 30 seconds and run the loop again. The ALB nodes may need a moment to distribute across both targets.
Also confirm both targets are healthy in the target group console. An unhealthy target receives zero traffic.
Help me figure out why my ALB is only routing to one server
Secret mission
Custom Health Check and Failover Testing
Your ALB is distributing traffic evenly across two healthy servers. But what happens when one server goes down? In this secret mission, you will create a dedicated health check endpoint, tighten the ALB's detection speed, then deliberately break a server to watch the load balancer reroute all traffic to the survivor in real time.
Clean Up Your Resources
Clean Up Your Resources
This project uses several AWS resources that incur hourly charges. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
Your infrastructure costs approximately $0.15-0.20 per hour while running. This includes two t3.medium instances ($0.0418/hr each), one t3.micro bastion ($0.0104/hr), a NAT Gateway ($0.045/hr plus data processing), and an Application Load Balancer ($0.0225/hr).
Even stopping EC2 instances does not eliminate all charges. The NAT Gateway and ALB continue to incur costs while they exist. Delete all resources when you are finished to avoid unexpected charges.
Resources you used:
- 2x t3.medium EC2 instances (WebLogic-Server-1 and WebLogic-Server-2).
- 1x t3.micro EC2 instance (Bastion-Host).
- 1x NAT Gateway with an associated Elastic IP.
- 1x Application Load Balancer (weblogic-alb) with target group (weblogic-tg).
- 1x VPC (weblogic-vpc) with 4 subnets, route tables, Internet Gateway, and 3 security groups.
Keep everything running
No action needed. Choose this if you are still actively testing your load balancer and WebLogic servers.
- Your resources will continue to incur charges at approximately $0.15-0.20 per hour.
- Monitor your AWS billing dashboard to avoid surprises.
Pause - I'll come back to this later
Stop your EC2 instances to save on compute costs. Note that the NAT Gateway and ALB will continue to charge while they exist.
- In the EC2 Console, select all three instances (WebLogic-Server-1, WebLogic-Server-2, and Bastion-Host).
- Click **Instance state**, then **Stop instance**.
- Confirm by clicking **Stop**.
- Your EC2 compute charges will stop, but the NAT Gateway ($0.045/hr) and ALB ($0.0225/hr) continue to incur charges.
- To resume later, select the instances and choose **Instance state**, then **Start instance**.
Delete - I don't want to use this again
Remove all project resources to eliminate ongoing charges. Delete resources in reverse order of creation to avoid dependency errors.
- In the EC2 Console, go to **Load Balancers**. Select weblogic-alb, click **Actions**, then **Delete load balancer**. Type confirm and click **Delete**.
- Go to **Target Groups**. Select weblogic-tg, click **Actions**, then **Delete**. Confirm the deletion.
- Go to **Instances**. Select all three instances (WebLogic-Server-1, WebLogic-Server-2, Bastion-Host). Click **Instance state**, then **Terminate instance**. Confirm by clicking **Terminate**.
- In the VPC Console, go to **NAT gateways**. Select your NAT Gateway, click **Actions**, then **Delete NAT gateway**. Type delete and confirm. Wait for the status to change to Deleted (this may take a minute).
- Go to **Elastic IPs**. Select the Elastic IP that was associated with your NAT Gateway. Click **Actions**, then **Release Elastic IP addresses**. Confirm the release.
- Go to **Your VPCs**. Select weblogic-vpc, click **Actions**, then **Delete VPC**. This will also delete the associated subnets, route tables, security groups, and Internet Gateway. Type delete and confirm.
Deletion order matters
You must delete the ALB before the VPC because the ALB depends on the VPC subnets. Similarly, the NAT Gateway must be deleted and its Elastic IP released before you can delete the VPC. If you see a dependency error, check that you have removed the ALB and NAT Gateway first.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete enterprise-grade AWS infrastructure with private application servers, secure bastion access, and load-balanced traffic distribution.
You've learned how to:
- Design a multi-tier VPC with public and private subnets, a bastion host for secure SSH jump access, and a NAT Gateway for outbound internet from isolated instances.
- Install and configure Oracle WebLogic Server 14c on private EC2 instances and deploy a Java web application that identifies which server is responding.
- Set up an Application Load Balancer with round-robin routing and verify traffic distribution by observing alternating server responses.
- Secret Mission: Implemented a custom health check endpoint and tested ALB failover behavior when a server goes down.
Ready to quiz yourself?