Liferay DXP Cluster with Load Balancing
1. Overview
This document provides a detailed explanation of every directive and block in the NGINX configuration file used to set up a reverse proxy and load balancer for a two-node Liferay DXP cluster running on a single machine (localhost). Each section is dissected line-by-line so that trainees and administrators gain a complete understanding of the role each directive plays.
2. Architecture Overview
The configuration implements the following layered architecture
Layer |
Component |
Port |
Role |
Client / Browser |
End User |
80 |
Sends HTTP requests |
Reverse Proxy |
NGINX |
80 |
Receives, routes, and forwards requests |
Backend Node 1 |
Liferay DXP Instance 1 |
8080 |
Serves application content |
Backend Node 2 |
Liferay DXP Instance 2 |
9090 |
Serves application content |
Key Principle: NGINX acts as the single entry point. All user traffic arrives at port 80 and is intelligently distributed across the two Liferay backend nodes. Users are unaware of the internal cluster topology.
3. Complete Configuration File
The full nginx.conf used in this setup is as follows:
worker_processes
1;
events
{
worker_connections
1024;
}
http
{
include
mime.types;
default_type
application/octet-stream;
sendfile
on;
keepalive_timeout
65;
#
1. Define your two local Liferay backend nodes
upstream
liferay_cluster {
ip_hash;
server
127.0.0.1:8080; # Liferay Node 1
server
127.0.0.1:9090; # Liferay Node 2
}
#
2. NGINX Front-end Server (Listens on port 80)
server
{
listen
80;
server_name
localhost;
location
/ {
proxy_pass
http://liferay_cluster;
proxy_set_header
Host $host;
proxy_set_header
X-Real-IP $remote_addr;
proxy_set_header
X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header
X-Forwarded-Proto $scheme;
proxy_redirect
off;
}
error_page
500 502 503 504 /50x.html;
location
= /50x.html {
root
html;
}
}
}
4. Global Block
4.1 worker_processes
worker_processes 1;
What it does: Defines how many NGINX worker processes will be started.
- Each worker process handles connections independently using non-blocking I/O.
- Value 1 means only one worker process is spawned — suitable for a single-CPU development or training environment.
- In production, this is typically set to auto to match the number of available CPU cores.
Value |
Use Case |
Notes |
1 |
Development / Training |
Simple, easy to debug |
auto |
Production |
Matches CPU core count automatically |
4 |
4-core server |
Manual override for specific tuning |
5. Events Block
events
{
worker_connections
1024;
}
5.1 worker_connections
What it does: Sets the maximum number of simultaneous connections a single worker process can handle.
- Value 1024 means each worker can handle up to 1024 concurrent connections.
- Total maximum connections = worker_processes x worker_connections = 1 x 1024 = 1024 connections.
- In production with auto workers on an 8-core machine: 8 x 1024 = 8192 total connections.
- This includes all connections: client-to-NGINX and NGINX-to-backend.
6. HTTP Block
The http block is the core of NGINX web server configuration. It contains all directives related to HTTP processing, upstream definitions, and virtual server configurations.
6.1 include mime.types
include mime.types;
What it does: Loads the MIME type mappings from the external file mime.types.
- MIME types tell the browser what kind of file is being sent (e.g., text/html for HTML, image/png for PNG files).
- Without this, the browser may not correctly render CSS, JavaScript, images, etc.
- The mime.types file is located in the NGINX conf/ directory.
6.2 default_type
default_type application/octet-stream;
What it does: Sets the fallback MIME type for any file whose type is not mapped in mime.types.
- application/octet-stream is the generic binary stream type.
- When a browser receives this, it will typically prompt the user to download the file.
6.3 sendfile
sendfile on;
What it does: Enables the use of the OS-level sendfile() system call for transferring files.
- Without sendfile: File data is copied to user-space memory, then written to the socket (2 copies).
- With sendfile on: Data is transferred directly from file descriptor to socket in kernel-space (0 user-space copies).
- This dramatically improves performance when serving static files.
- For a proxy scenario (like ours), its impact is limited since we are not serving files directly.
6.4 keepalive_timeout
keepalive_timeout 65;
What it does: Sets how long (in seconds) NGINX will keep an idle client connection open (HTTP keep-alive).
- Value 65 seconds: After the last response, NGINX waits 65 seconds before closing the connection.
- Why keep connections alive? Opening a new TCP connection has overhead (3-way handshake). Keep-alive allows reuse.
- This is especially important for Liferay portals because a single page load triggers many HTTP requests (JS, CSS, images).
7. Upstream Block — Defining the Cluster
upstream
liferay_cluster {
ip_hash;
server
127.0.0.1:8080; # Liferay Node 1
server
127.0.0.1:9090; # Liferay Node 2
}
The upstream block defines a named group of backend servers. NGINX refers to this group by name when proxying requests.
7.1 upstream liferay_cluster
Name: liferay_cluster — This is a logical name assigned to the group of backend servers. It is referenced in the proxy_pass directive.
7.2 ip_hash — Sticky Sessions
ip_hash;
What it does: Enables session persistence (sticky sessions) based on the client's IP address.
- NGINX computes a hash of the client's source IP address.
- All requests from that IP are always routed to the same backend server.
- This is critical for Liferay because Liferay stores session state (logged-in user, portlet state) in memory on the node.
Why ip_hash is needed for Liferay
Without ip_hash |
With ip_hash |
User logs in to Node 1 |
User logs in to Node 1 |
Next request goes to Node 2 |
Next request also goes to Node 1 |
Node 2 has no session — user is logged out! |
Session is intact — user stays logged in |
Broken user experience |
Seamless user experience |
Alternative: If Liferay cluster is properly configured with a shared session store (e.g., Infinispan/Ehcache), then ip_hash may not be required. However, for single-machine demo/training environments, ip_hash is the simplest and most reliable approach.
7.3 Backend Server Definitions
server
127.0.0.1:8080; # Liferay Node 1
server
127.0.0.1:9090; # Liferay Node 2
Each server directive registers a backend node in the upstream group
Directive |
IP Address |
Port |
Description |
server 127.0.0.1:8080 |
127.0.0.1 (localhost) |
8080 |
Liferay DXP Node 1 — first Tomcat instance |
server 127.0.0.1:9090 |
127.0.0.1 (localhost) |
9090 |
Liferay DXP Node 2 — second Tomcat instance |
- Both instances run on the same machine (127.0.0.1) but listen on different ports.
- In production, these would be different IP addresses on separate physical or virtual machines.
- Since ip_hash is active, load balancing algorithm assigns each client IP deterministically to one of these nodes.
8. Server Block — Virtual Server
server
{
listen
80;
server_name
localhost;
...
8.1 listen 80
What it does: Instructs NGINX to listen for incoming HTTP connections on TCP port 80.
- Port 80 is the default HTTP port. Browsers connect here when no explicit port is specified in the URL.
- To enable HTTPS (TLS), you would add a second server block listening on port 443 with SSL certificates.
8.2 server_name localhost
What it does: Defines which domain name(s) this server block responds to.
- Value localhost: This virtual server only responds when the HTTP request's Host header is localhost.
- In production, this would be set to your domain name, e.g., server_name portal.company.com;
- Multiple names are supported: server_name example.com www.example.com;
9. Location Block — Proxy Configuration
location
/ {
proxy_pass
http://liferay_cluster;
proxy_set_header
Host $host;
proxy_set_header
X-Real-IP $remote_addr;
proxy_set_header
X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header
X-Forwarded-Proto $scheme;
proxy_redirect
off;
}
9.1 location /
What it does: Matches all incoming request paths and applies the nested directives.
- The / pattern is the most general location prefix — it matches everything.
- NGINX uses the most specific matching location block. Since / is the only one here, all traffic is handled here.
9.2 proxy_pass
proxy_pass http://liferay_cluster;
What it does: Forwards the incoming client request to the upstream group named liferay_cluster.
- NGINX applies the ip_hash algorithm and selects either 127.0.0.1:8080 or 127.0.0.1:9090.
- NGINX acts as an intermediary: receives from client, forwards to backend, receives backend response, sends to client.
- The client never knows which backend node served the request.
9.3 Proxy Headers (Deep Explanation)
These headers pass important client metadata from NGINX to Liferay. Without them, Liferay would see all requests as coming from NGINX (127.0.0.1) rather than the actual client.
9.3.1 proxy_set_header Host $host
proxy_set_header Host $host;
$host: NGINX variable containing the value of the Host header from the original client request.
Without this, Liferay receives the upstream group name (liferay_cluster) as the Host, which causes URL generation errors. Liferay uses the Host header to generate absolute URLs in portal pages.
9.3.2 proxy_set_header X-Real-IP $remote_addr
proxy_set_header X-Real-IP $remote_addr;
$remote_addr: The actual IP address of the connecting client.
Passes the original client IP address to Liferay via the X-Real-IP custom header. Liferay uses this for audit logging, access control, geolocation, and rate limiting.
9.3.3 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
$proxy_add_x_forwarded_for: Appends the client's IP to any existing X-Forwarded-For header.
This is the standard header for passing the full chain of proxy IP addresses. If there are multiple proxies in the chain: X-Forwarded-For: client-ip, proxy1-ip, proxy2-ip. Liferay reads this to identify the true originating client.
9.3.4 proxy_set_header X-Forwarded-Proto $scheme
proxy_set_header X-Forwarded-Proto $scheme;
$scheme: The protocol used by the client — either http or https.
This is critical for SSL termination. If NGINX handles HTTPS and forwards as HTTP internally: $scheme = https. Liferay reads X-Forwarded-Proto to decide whether to generate https:// or http:// URLs and to enforce security policies.
9.4 proxy_redirect off
proxy_redirect off;
What it does: Disables automatic rewriting of Location and Refresh headers in the backend's HTTP redirects.
- When a backend server sends a redirect (e.g., HTTP 302 to http://127.0.0.1:8080/web/guest), NGINX would normally try to rewrite the URL.
- With proxy_redirect off, NGINX passes the Location header unchanged.
- Since our proxy_set_header Host directive already passes the correct host to Liferay, Liferay generates correct redirect URLs automatically.
- This prevents double-rewriting which could corrupt redirect URLs.
10. Error Page Configuration
error_page
500 502 503 504 /50x.html;
location
= /50x.html {
root
html;
}
10.1 error_page directive
What it does: Maps specific HTTP error status codes to a custom error page URI.
HTTP Status Code |
Meaning |
Common Cause |
500 |
Internal Server Error |
Unhandled exception in Liferay application |
502 |
Bad Gateway |
Backend (Liferay) is down or not responding |
503 |
Service Unavailable |
Backend overloaded or in maintenance |
504 |
Gateway Timeout |
Backend took too long to respond (timeout) |
10.2 Custom Error Page Location
location
= /50x.html {
root
html;
}
- location = /50x.html: Exact match — only handles the path /50x.html.
- root html: Serves the file from the html/ directory within NGINX's installation directory.
- NGINX ships with a default 50x.html file at nginx/html/50x.html.
11. Complete Directive Reference Table
Directive |
Block |
Value / Setting |
Purpose |
worker_processes |
Global |
1 |
Number of NGINX worker processes |
worker_connections |
events |
1024 |
Max connections per worker |
include |
http |
mime.types |
Load MIME type definitions |
default_type |
http |
application/octet-stream |
Fallback MIME type |
sendfile |
http |
on |
Kernel-space file transfer optimization |
keepalive_timeout |
http |
65 |
Idle connection lifetime (seconds) |
upstream |
http |
liferay_cluster |
Define backend server group |
ip_hash |
upstream |
— |
Sticky sessions by client IP |
server (upstream) |
upstream |
127.0.0.1:8080/9090 |
Backend Liferay node addresses |
listen |
server |
80 |
NGINX listens on HTTP port 80 |
server_name |
server |
localhost |
Virtual host name |
proxy_pass |
location |
http://liferay_cluster |
Forward requests to upstream |
proxy_set_header Host |
location |
$host |
Pass original Host header |
proxy_set_header X-Real-IP |
location |
$remote_addr |
Pass client IP to backend |
proxy_set_header X-Forwarded-For |
location |
$proxy_add_x_forwarded_for |
Pass proxy chain IPs |
proxy_set_header X-Forwarded-Proto |
location |
$scheme |
Pass protocol (http/https) |
proxy_redirect |
location |
off |
Disable redirect URL rewriting |
error_page |
server |
500 502 503 504 |
Custom error page mapping |
2. Production Recommendations
While this configuration is ideal for training and demo purposes, the following enhancements are recommended for production deployments:
12.1 Enable HTTPS/TLS
- Add a second server block listening on port 443 with ssl certificate paths.
- Redirect HTTP to HTTPS using a return 301 https://$host$request_uri directive.
12.2 Increase Worker Processes
worker_processes auto;
Use auto to match the number of CPU cores available on the server.
12.3 Add Health Checks
upstream
liferay_cluster {
ip_hash;
server
127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server
127.0.0.1:9090 max_fails=3 fail_timeout=30s;
}
- max_fails=3: After 3 failed connection attempts, mark server as unavailable.
- fail_timeout=30s: Server is considered unavailable for 30 seconds.
12.4 Tune Timeouts
proxy_connect_timeout
60s;
proxy_send_timeout
60s;
proxy_read_timeout
300s;
proxy_read_timeout should be increased for Liferay portals because some requests (reports, document export) can take several minutes.
12.5 Buffer Configuration
proxy_buffer_size
128k;
proxy_buffers
4 256k;
proxy_busy_buffers_size
256k;
Liferay responses can contain large cookies and headers. Increase buffer sizes to prevent buffering errors.
Comments
Post a Comment