
Every sysadmin who has been handed a ticket that says “set up Jira by Friday” knows the feeling. It sounds simple on paper, download an installer, click through a wizard, done. Then reality kicks in: database encoding mismatches, a JVM that refuses to start because of memory limits, a reverse proxy that silently breaks Jira’s base URL, and a client wondering why the instance is timing out during a sprint planning session. Jira is not a fragile application, but it is opinionated, and Ubuntu 26.04 LTS, codenamed Resolute Raccoon, brings enough platform changes that a “just copy the old guide” approach will bite you.
This release matters more than most Ubuntu bumps. Canonical shipped Resolute Raccoon on April 23, 2026 with Linux kernel 7.0, GNOME 50, a Wayland-only desktop stack, the early rollout of sudo-rs, and, critically for this guide, PostgreSQL 18 as the default database package in the repositories. Standard security support runs until April 2031, with Ubuntu Pro extending that to a full decade. If you’re standing up Jira on this LTS, you’re likely doing it because you want five-plus years of runway without a forced OS migration mid-project, and that’s a reasonable bet.
This article walks through installing Jira Software (the self-managed Data Center-track release, since Atlassian retired new Server licenses years ago) on a fresh Ubuntu 26.04 LTS server, using PostgreSQL as the backing database, Nginx as a reverse proxy with TLS termination, and a hardened systemd service configuration. It also covers the JVM tuning, firewall rules, backup strategy, and the troubleshooting scenarios that actually show up in production, not the sanitized happy path you get from a five-minute quickstart. Whether you’re running this on a bare-metal box in a Yogyakarta data center or a cloud VM on DigitalOcean or AWS, the fundamentals below apply the same way.
Why Ubuntu 26.04 LTS Changes the Installation Calculus
Before touching a terminal, it’s worth understanding what’s different this time around. Resolute Raccoon isn’t a cosmetic refresh over Noble Numbat (24.04); the underlying package set shifted enough that copy-pasting a 24.04 tutorial verbatim will cause subtle failures.
The two changes that directly affect a Jira deployment:
- PostgreSQL 18 is now the default in the Ubuntu repos, replacing PostgreSQL 16 from the previous LTS. Jira’s JDBC driver compatibility and
pg_hba.confauthentication defaults behave slightly differently, and the data directory path convention (/etc/postgresql/18/main/) trips up anyone who scripts config edits with a hardcoded version number. - cgroup v1 is effectively gone in favor of a pure cgroup v2 stack, which matters if you’re containerizing Jira or running it under systemd resource limits, since some older
MemoryLimit=style directives behave differently under v2 semantics.
None of this is a dealbreaker. It just means the installation steps below are written specifically against this release rather than assuming backward compatibility.
Prerequisites Before You Start
Get these sorted first, because troubleshooting a half-configured server is far more painful than spending fifteen minutes upfront.
- A clean Ubuntu 26.04 LTS server (minimum 4 GB RAM for evaluation, 8 GB or more for a real team of 25+ users; Jira’s JVM alone wants a healthy heap).
- Root or sudo access.
- A registered domain name if you plan to expose Jira publicly with TLS (recommended over raw IP access).
- Atlassian account to generate a trial or purchased license key.
- Basic familiarity with
systemd,ufw, and PostgreSQL’spsqlshell.
Run a full system update before anything else. Skipping this step is how you end up debugging a library conflict three hours into the install instead of catching it at the source.
sudo apt update && sudo apt upgrade -y
sudo reboot
Reboot if the kernel was patched. It’s a small habit, but running Jira on a half-updated kernel with pending module reloads is asking for weird, hard-to-reproduce bugs later.
Step 1: Install and Configure PostgreSQL 18
Jira supports PostgreSQL, MySQL, Oracle, and SQL Server, but PostgreSQL is the pragmatic default for self-managed instances, it’s open source, well-documented, and Atlassian’s own performance benchmarks favor it for Data Center deployments.
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
sudo systemctl status postgresql
Confirm you’re running version 18, since that’s the repository default on Resolute Raccoon:
psql --version
Now create a dedicated database and user. Never reuse the postgres superuser role for application connections; that’s a habit that comes back to haunt you during a security audit.
sudo -u postgres psql
Inside the psql shell:
CREATE ROLE jirauser WITH LOGIN PASSWORD 'ReplaceWithAStrongPassword!';
CREATE DATABASE jiradb WITH ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C' TEMPLATE template0 OWNER jirauser;
GRANT ALL PRIVILEGES ON DATABASE jiradb TO jirauser;
\q
The LC_COLLATE 'C' LC_CTYPE 'C' combination isn’t cosmetic. Jira explicitly requires C-locale collation on PostgreSQL databases; using the system default locale (often en_US.UTF-8) causes indexing and sorting inconsistencies that surface later as bizarre search results or JQL ordering bugs. This is one of those requirements that’s easy to overlook and painful to fix retroactively, because changing collation on a live database means a full dump-and-restore.
Next, tell PostgreSQL to accept password-authenticated connections from Jira. Edit the host-based authentication file:
sudo nano /etc/postgresql/18/main/pg_hba.conf
Add or adjust this line so local TCP connections use md5 (or scram-sha-256, which is the more secure modern default) rather than peer:
host jiradb jirauser 127.0.0.1/32 scram-sha-256
Restart PostgreSQL to apply changes:
sudo systemctl restart postgresql
Test the connection before moving forward, because catching a credential typo now saves a confusing failure during Jira’s setup wizard later:
psql -h 127.0.0.1 -U jirauser -d jiradb -W
Step 2: Install Java (OpenJDK)
Jira ships with a bundled JRE in its installer, so a system-wide JDK isn’t strictly mandatory, but having one available is useful for diagnostics, heap dump analysis, and running supplementary tooling like Confluence or third-party plugins that expect JAVA_HOME to be set.
sudo apt install -y openjdk-21-jre-headless
java -version
Set JAVA_HOME in your shell profile if other tools need it:
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' | sudo tee -a /etc/environment
source /etc/environment
Step 3: Download and Install Jira Software
At the time of writing, Jira Software 11.3.x is the current self-managed release line. Always check Atlassian’s official download page for the latest patch version before pulling the binary, security fixes ship regularly and running an outdated point release on a public-facing server is an avoidable risk.
cd /tmp
wget https://product-downloads.atlassian.com/software/jira/downloads/atlassian-jira-software-11.3.2-x64.bin
chmod a+x atlassian-jira-software-11.3.2-x64.bin
sudo ./atlassian-jira-software-11.3.2-x64.bin
The installer walks through an interactive text wizard. Here’s what to choose at each prompt, based on what actually holds up in production:
- Installation type: choose “Custom Install” if you want control over paths, or “Express Install” for a quick evaluation. For anything touching production, go custom.
- Installation directory: default is
/opt/atlassian/jira, which is fine. Keep application binaries and data separate. - Home directory: default is
/var/atlassian/application-data/jira. This is where attachments, indexes, and plugin data live, make sure this path sits on a disk with enough IOPS headroom, because Lucene indexing under Jira’s home directory is disk-intensive during large searches or bulk imports. - Ports: default HTTP port 8080, control port 8005. If you’re running Confluence or Bitbucket on the same host, you’ll need to change these to avoid collisions.
- Run as service: yes, always. This registers Jira as a systemd-managed service, which means it survives reboots and integrates with
systemctlfor monitoring.
Once the installer finishes, start Jira if it hasn’t already:
sudo systemctl start jira
sudo systemctl enable jira
sudo systemctl status jira
Tail the logs while it boots, since first startup takes a few minutes as Jira initializes its internal Lucene index and Tomcat container:
tail -f /opt/atlassian/jira/logs/catalina.out
Step 4: Complete the Setup Wizard
Open a browser and navigate to http://your-server-ip:8080. You’ll see the Jira setup wizard. Choose “I’ll set it up myself”, not the automatic option, since automatic mode provisions an embedded H2 database that is explicitly unsupported for production use and will bite you the moment your dataset grows past a trivial size.
On the database configuration screen:
- Database Type: PostgreSQL
- Hostname:
127.0.0.1 - Port:
5432 - Database:
jiradb - Username:
jirauser - Password: the password set earlier
Click Test Connection. If it fails here, it’s almost always one of three things: pg_hba.conf not reloaded, a typo in the password, or PostgreSQL listening only on a socket rather than TCP. Once it passes, Jira will run its schema migration, enter your license key, create the initial admin account, and you’re through the wizard.
Step 5: Configure Nginx as a Reverse Proxy with TLS
Running Jira directly on port 8080 exposed to the internet is a bad look and a worse security posture. Put it behind Nginx.
sudo apt install -y nginx
Create a server block:
sudo nano /etc/nginx/sites-available/jira.conf
server {
listen 80;
server_name jira.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8080;
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_read_timeout 120s;
client_max_body_size 100M;
}
}
Enable it and reload Nginx:
sudo ln -s /etc/nginx/sites-available/jira.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Now issue a Let’s Encrypt certificate with Certbot:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d jira.yourdomain.com
There’s one step people forget constantly: Jira needs to know it’s sitting behind a proxy, otherwise generated links (email notifications, webhook callbacks, attachment URLs) will point to the internal :8080 address instead of your public domain. Go to Jira Administration → System → General Configuration and set the Base URL to https://jira.yourdomain.com. Then edit Jira’s Tomcat connector config to trust the proxy headers:
sudo nano /opt/atlassian/jira/conf/server.xml
Add scheme="https" and proxyName/proxyPort attributes to the <Connector> block, or better, enable the RemoteIpValve that ships with Tomcat, which reads the X-Forwarded-* headers Nginx is already sending. This is the fix for the classic “mixed content” and “redirect loop” symptoms people report after adding SSL.
Step 6: Firewall and Network Hardening
With Nginx handling public traffic, lock down direct access to Jira’s Tomcat port.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw deny 8080
sudo ufw enable
sudo ufw status verbose
If Jira and PostgreSQL live on separate hosts, restrict PostgreSQL’s listener to the private network interface only, and never expose port 5432 to the public internet. On a single-host setup, 127.0.0.1 binding (the default) already handles this.
JVM and Performance Tuning
Jira’s default JVM heap settings are conservative and tuned for small evaluation instances, not real team usage. Edit the startup config:
sudo nano /opt/atlassian/jira/bin/setenv.sh
Adjust the heap sizes based on available RAM. A rough rule of thumb for a mid-sized team (50-150 users): allocate 2-4 GB minimum heap and let it scale up to 6-8 GB max, leaving enough headroom for the OS page cache and PostgreSQL’s own memory footprint.
JVM_MINIMUM_MEMORY="2048m"
JVM_MAXIMUM_MEMORY="6144m"
JVM_SUPPORT_RECOMMENDED_ARGS="-XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
The G1 garbage collector handles Jira’s mixed allocation patterns (lots of short-lived objects during indexing, longer-lived caches for issue data) better than the older CMS collector, which Atlassian deprecated in newer JVM builds anyway.
A few additional tuning notes worth internalizing:
- Disk I/O matters more than CPU for Jira’s day-to-day responsiveness. The Lucene search index under
<jira-home>/caches/indexesbenefits enormously from SSD-backed storage. If you’re on spinning disks in 2026, that’s the first thing to fix before touching JVM flags. - PostgreSQL’s
shared_buffersandwork_memin/etc/postgresql/18/main/postgresql.confshould scale with your server’s RAM, roughly 25% of total memory forshared_bufferson a dedicated database host. - Reindex during low-traffic windows. A full Jira reindex on a large instance can spike CPU and disk I/O noticeably; schedule it outside business hours if you’re running it manually rather than relying on the background reindex.
Backup Strategy
A Jira instance without a tested backup is a production incident waiting for a date. At minimum, back up two things separately: the PostgreSQL database and the Jira home directory (attachments, plugins, configuration).
pg_dump -U jirauser -h 127.0.0.1 jiradb | gzip > /backups/jiradb_$(date +%F).sql.gz
tar -czf /backups/jira_home_$(date +%F).tar.gz /var/atlassian/application-data/jira
Automate this with a cron job, and, this is the part people skip, actually restore from backup on a scratch VM every few months to confirm the backup is usable. A backup you’ve never restored is a hypothesis, not a safety net.
Troubleshooting Common Errors
Jira won’t start, log shows “Port 8080 already in use.”
Something else is bound to that port, likely another Tomcat instance or a leftover process from a failed prior install. Find and kill it:
sudo lsof -i :8080
sudo kill -9 <PID>
Setup wizard fails at database test connection with “FATAL: Peer authentication failed.”
This means pg_hba.conf still has peer or ident authentication for that connection type instead of md5/scram-sha-256. Recheck the file and restart PostgreSQL after editing, a reload sometimes isn’t enough if the connection method itself changed.
Jira runs but attachments fail to upload with a 413 error.
Nginx’s default client_max_body_size is 1 MB, far too small for attachments or import files. Bump it in the server block, as shown earlier, and reload Nginx.
Reindexing gets stuck or times out.
Usually a memory pressure symptom. Check atlassian-jira.log for OutOfMemoryError. Increase JVM_MAXIMUM_MEMORY in setenv.sh, restart Jira, and retry during a quiet period.
“This XML file does not appear to have any style information” or blank page after enabling HTTPS.
Classic symptom of a missing RemoteIpValve configuration or an incorrect Base URL setting. Double-check server.xml and the Base URL field in Jira’s General Configuration.
High CPU usage under normal load with no obvious cause.
Check for a runaway plugin. Jira Marketplace add-ons are a common source of background job loops that spike CPU. Disable recently installed plugins one at a time via Manage Apps to isolate the culprit.