Hello Atlassian Support,
I have deployed Jira Data Center on Azure Kubernetes Service (AKS) using an Internal Load Balancer (ILB) and mapped it with a DNS entry.
When I deploy Jira with only one replica, everything works fine — I can log in to the system admin page and navigate to all settings without any issues.
However, when I deploy Jira with two replicas, I face persistent issues where the system throws session expired errors and shows XSRF token validation errors on almost every page.
This makes Jira unusable in a multi-replica deployment scenario.
I am sharing our NGINX site configuration and Jira server.xml file for review to help troubleshoot the session expired / XSRF token issue when Jira is deployed with two replicas.
Please note: These files have been sanitized to remove sensitive information. The following placeholders have been used:
<JIRA_SERVER_FQDN> → Actual Jira server DNS name
<JIRA_BACKEND_IP> → Internal IP of Jira backend service in AKS
<CERTIFICATE_FILE> → SSL certificate file name/path
<PRIVATE_KEY_FILE> → SSL private key file name/path
The file structure, directives, and settings are otherwise unchanged to ensure accurate troubleshooting.
Let me know if you need any more details, the original (secure) values can be provided privately if absolutely necessary.
Below are the file config :
nginx.conf / Jira site config (sanitized)
server {
listen 443 ssl;
server_name <JIRA_SERVER_FQDN>; # Masked hostname
# SSL configuration
ssl_certificate /etc/nginx/ssl/<CERTIFICATE_FILE>;
ssl_certificate_key /etc/nginx/ssl/<PRIVATE_KEY_FILE>;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:...'; # Truncated for brevity
ssl_prefer_server_ciphers on;
# Security headers (Optional but recommended)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
client_max_body_size 100M;
location / {
# app_protect_enable on;
proxy_pass http://<JIRA_BACKEND_IP>:8080; # Masked internal IP
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
server {
listen 80;
server_name <JIRA_SERVER_FQDN>; # Masked hostname
return 301 https://$host$request_uri;
}
server.xml (sanitized)
<?xml version="1.0" encoding="utf-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<Service name="Catalina">
<Connector port="8080"
maxThreads="100"
minSpareThreads="10"
connectionTimeout="20000"
enableLookups="false"
protocol="HTTP/1.1"
redirectPort="8443"
acceptCount="10"
secure="true"
scheme="https"
proxyName="<JIRA_SERVER_FQDN>" <!-- Masked hostname -->
proxyPort="443"
relaxedPathChars="[]|"
relaxedQueryChars="[]|{}^`\"<>"
bindOnInit="false"
maxHttpHeaderSize="8192"
useBodyEncodingForURI="true"
disableUploadTimeout="true" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="" docBase="${catalina.home}/atlassian-jira"
reloadable="false" useHttpOnly="true">
<Resource name="UserTransaction"
auth="Container"
type="javax.transaction.UserTransaction"
factory="org.objectweb.jotm.UserTransactionFactory"
jotm.timeout="60"/>
<Manager pathname=""/>
<JarScanner scanManifest="false"/>
<Valve className="org.apache.catalina.valves.StuckThreadDetectionValve" threshold="120" />
</Context>
</Host>
<Valve className="org.apache.catalina.valves.AccessLogValve"
pattern="%a %{jira.request.id}r %{jira.request.username}r %t "%m %U%q %H" %s %b %D "%{Referer}i" "%{User-Agent}i" "%{jira.request.assession.id}r""
requestAttributesEnabled="false"
maxDays="-1"/>
</Engine>
</Service>
</Server>
I have also attached the screenshot for your reference.
Could you please help me resolve this issue?
Thank you for your help.
You need to configure session affinity aka sticky sessions on your LoadBalancer which is pre-requisite of running datacenter apps (not only on kube)
Thanks for confirming that sticky sessions are required.
Could you please guide me on the correct configuration for our setup?
We are running Jira Data Center on AKS with an Internal Load Balancer in front of NGINX.
Specifically:
Should sticky sessions be configured on the Azure ILB, in NGINX, or both?
What’s the Jira-recommended NGINX config snippet for handling JSESSIONID-based sticky sessions?
Are there any Jira application settings or cluster.properties changes needed to complement this?
This will help ensure we configure it exactly as per best practices for Jira on Kubernetes.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
If you have nginx ingress controller, and you dpeloy using official Helm charts, then it should work out of the box.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Thanks for your response @Yevhen
Just to clarify our architecture: we are not using the Kubernetes NGINX Ingress Controller. Instead, our setup consists of:
Given the ILB operates at Layer 4 (TCP) and does not terminate HTTP, how do you recommend we configure session affinity (sticky sessions) in this architecture?
Specifically:
Should we rely on Azure ILB’s source IP affinity at Layer 4, or configure cookie-based sticky sessions at the external NGINX reverse proxy instead?
If NGINX is the appropriate layer, could you please provide Jira Data Center’s recommended NGINX reverse proxy configuration for sticky sessions?
We want to ensure our setup meets Jira’s official best practices for Data Center deployments in Kubernetes with an Azure Internal Load Balancer (ILB).
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I'd say, Azure LB needs to use sticky cookies and send requests to the same backend Jira pods.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Regarding your suggestion that the Azure Load Balancer should use sticky cookies:
In our environment, we are using an Azure Internal Load Balancer (ILB), which operates at Layer 4 (TCP). Based on Microsoft’s documentation, the ILB:
Supports session persistence (session affinity/sticky sessions) only via client source IP.
Does not support cookie-based session affinity, because it cannot inspect HTTP traffic or cookies.
Therefore, in our architecture:
IP-based stickiness is handled at the ILB layer (ensuring a given client IP is sent to the same NGINX reverse proxy instance).
Cookie-based stickiness (required for Jira’s JSESSIONID sessions) will be configured at the NGINX reverse proxy level, which operates at Layer 7 and can read HTTP cookies.
If cookie-based stickiness at the load balancer level was required, we would need a Layer 7 load balancer such as Azure Application Gateway — but our setup uses ILB + NGINX.
Could you please confirm that implementing JSESSIONID-based sticky sessions in NGINX is aligned with Jira Data Center best practices?
Thanks.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.