Deploy SonarQube on Ubuntu 24.04 for Production (2026)

Introduction

If your team is merging code without any automated quality gates — no static analysis, no vulnerability scanning, no duplication checks — you're basically flying blind. You'll catch the obvious bugs in review, sure, but the subtle ones? The security issues hiding in that third-party integration? Those slip through until they don't, and at that point it's either a late-night hotfix or a very uncomfortable conversation with your client.

SonarQube is the tool that fills that gap. It runs static analysis on your codebase, flags bugs, security vulnerabilities, code smells, and duplicated logic — and it does it automatically on every push if you wire it into your CI pipeline. The Community Edition is free, open source, and genuinely powerful for most teams.

In this guide, I'll walk you through deploying SonarQube on a fresh Ubuntu 24.04 server the right way — with PostgreSQL as the backend database, a proper systemd service, and Nginx as a reverse proxy in front. This is a production setup, not a Docker one-liner that works until it doesn't.


How the Stack Fits Together

Before touching the terminal, it's worth understanding what you're actually deploying here, because SonarQube isn't just a single process — it's three services bundled together, plus an external database and a web server in front.

  • PostgreSQL: SonarQube's persistence layer. It stores analysis results, project configuration, user accounts, and quality profiles. Do not use the embedded H2 database in production — it's for evaluation only and not supported for serious use.
  • SonarQube (Elasticsearch + Web + Compute Engine): Three internal services. Elasticsearch indexes your code data. The Web server handles the UI and API. The Compute Engine processes analysis reports asynchronously.
  • Nginx: The public entry point. It terminates HTTPS and proxies requests to SonarQube's internal port (default 9000). Your SonarQube instance should never be directly exposed to the internet.
  • systemd: Keeps SonarQube alive across reboots and handles crash recovery without you needing to babysit it.

Step 1: Prepare the Ubuntu Server

Start with a clean Ubuntu 24.04 server — at minimum 4 GB RAM, though 8 GB is more comfortable once you have a few projects scanning. SonarQube's Elasticsearch component is hungry. I've seen it get OOM-killed on underpowered instances, and it is not a fun debugging experience.

Update the System

sudo apt update && sudo apt upgrade -y

Install Required Packages

sudo apt install -y curl wget unzip gnupg2 nginx

Configure Firewall

sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
Important: Allow SSH before enabling UFW. If you enable the firewall without the SSH rule on a remote machine, you will lock yourself out and need console access to recover.

Step 2: Install Java (OpenJDK 21)

SonarQube runs on the JVM. While older versions relied heavily on Java 17, SonarQube 10.6 and newer natively support Java 21. We will install the Java 21 SDK to ensure you have the latest LTS runtime features and performance improvements.

sudo apt install -y openjdk-21-jdk

Verify it's installed correctly:

java -version

You should see output referencing OpenJDK 21. If you have multiple Java versions installed, set 21 as the default:

sudo update-alternatives --config java
Version Warning: Because you are using Java 21, ensure you download SonarQube version 10.6 or higher in Step 5. Older versions (like 10.4) will fail to start on Java 21.

Step 3: Set Up PostgreSQL

SonarQube needs a dedicated PostgreSQL database. The embedded H2 that ships with SonarQube is explicitly not supported for production — Sonar's own documentation says so clearly. Use PostgreSQL.

Install PostgreSQL

sudo apt install -y postgresql postgresql-contrib
sudo systemctl start postgresql
sudo systemctl enable postgresql

Create the Database and User

sudo -u postgres psql

Once inside the PostgreSQL prompt:

CREATE USER sonarqube WITH PASSWORD 'your_strong_password_here';
CREATE DATABASE sonarqube OWNER sonarqube;
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO sonarqube;
\q
Note: Use a strong, randomly generated password here. Store it somewhere secure — you'll need it when configuring SonarQube in the next steps.

Step 4: Tune Kernel Parameters

This is the step most tutorials skim over, and then people wonder why Elasticsearch refuses to start or crashes under load. SonarQube's embedded Elasticsearch needs two kernel-level settings adjusted before it'll run properly in production.

Increase Virtual Memory Map Limit

sudo nano /etc/sysctl.conf

Add these lines at the bottom:

vm.max_map_count=524288
fs.file-max=131072

Apply the changes immediately without rebooting:

sudo sysctl -p

Increase File Descriptor Limits

sudo nano /etc/security/limits.conf

Add these lines:

sonarqube   -   nofile   131072
sonarqube   -   nproc    8192

These limits apply to the sonarqube system user we'll create shortly. If you skip this, Elasticsearch will hit OS-level limits and start dropping connections or refusing to open index files — usually at the worst possible moment.


Step 5: Download and Install SonarQube

Create a Dedicated System User

SonarQube must not run as root. Create a dedicated user with no login shell:

sudo useradd -r -m -U -d /opt/sonarqube -s /bin/bash sonarqube

Download SonarQube Community Edition

cd /tmp
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-10.6.0.92863.zip
Heads up: Check the official downloads page for the latest Community Edition version number before running this command. Make sure it is 10.6+ to ensure compatibility with Java 21.

Extract and Install

sudo unzip sonarqube-10.6.0.92863.zip -d /opt
sudo mv /opt/sonarqube-10.6.0.92863 /opt/sonarqube
sudo chown -R sonarqube:sonarqube /opt/sonarqube
sudo chmod -R 755 /opt/sonarqube

Configure the Database Connection

sudo nano /opt/sonarqube/conf/sonar.properties

Find and update these properties (most are commented out by default — uncomment and fill them in):

# Database connection
sonar.jdbc.username=sonarqube
sonar.jdbc.password=your_strong_password_here
sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqube
 
# Web server settings
sonar.web.host=127.0.0.1
sonar.web.port=9000
 
# Elasticsearch data path (optional but recommended)
sonar.path.data=/opt/sonarqube/data
sonar.path.temp=/opt/sonarqube/temp

Setting sonar.web.host=127.0.0.1 ensures SonarQube only listens on localhost. Nginx will proxy traffic to it — the SonarQube port should never be publicly reachable on its own.


Step 6: Create a systemd Service

Running SonarQube manually in a terminal is fine for testing. For production, you want it managed by systemd so it starts automatically on boot and restarts after crashes without any manual intervention.

sudo nano /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube Service
After=network.target postgresql.service
Requires=postgresql.service
 
[Service]
Type=forking
ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop
User=sonarqube
Group=sonarqube
Restart=on-failure
RestartSec=10
LimitNOFILE=131072
LimitNPROC=8192
 
[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable sonarqube
sudo systemctl start sonarqube

Check the startup status. SonarQube takes a minute or two to initialize — be patient:

sudo systemctl status sonarqube

If something goes wrong, the logs are your first stop:

tail -f /opt/sonarqube/logs/sonar.log
tail -f /opt/sonarqube/logs/web.log
tail -f /opt/sonarqube/logs/es.log

Step 7: Configure Nginx as a Reverse Proxy

With SonarQube running on port 9000 internally, the next step is pointing Nginx at it so the outside world can reach it through a proper domain and (eventually) HTTPS.

sudo nano /etc/nginx/sites-available/sonarqube.conf
server {
    listen 80;
    server_name sonar.yourdomain.com;
 
    access_log  /var/log/nginx/sonarqube.access.log;
    error_log   /var/log/nginx/sonarqube.error.log;
 
    location / {
        proxy_pass         http://127.0.0.1:9000;
        proxy_http_version 1.1;
 
        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_connect_timeout  300;
        proxy_send_timeout     300;
        proxy_read_timeout     300;
 
        client_max_body_size   100M;
    }
}
# Enable the config
sudo ln -s /etc/nginx/sites-available/sonarqube.conf /etc/nginx/sites-enabled/
 
# Always test before restarting
sudo nginx -t
 
# Restart Nginx
sudo systemctl restart nginx

The generous timeouts (300s) matter here. SonarQube analysis uploads — especially from large codebases — take longer than the default 60 seconds. If you leave Nginx at defaults, you'll get mysterious 504 errors during large scans.

Add HTTPS with Certbot

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d sonar.yourdomain.com

Follow the prompts. Certbot modifies your Nginx config to handle SSL and sets up auto-renewal. Your domain's DNS needs to point to this server's IP before Certbot will work.


Step 8: First Login and Initial Setup

Open your browser and navigate to https://sonar.yourdomain.com. The default credentials are:

  • Username: admin
  • Password: admin

SonarQube will immediately prompt you to change the admin password. Do it — don't skip this. From there, a few things to do right away:

  • Go to Administration → Security and disable the "Allow anyone to create projects" option unless you actually want open enrollment.
  • Set up your first project and generate an analysis token under My Account → Security → Generate Tokens. This token is what your CI pipeline uses to push analysis results.
  • If you're integrating with GitHub or GitLab, configure the DevOps platform under Administration → DevOps Platform Integrations.

Common Mistakes

Mistake What Happens Fix
Using the embedded H2 database Data corruption risk, no support, performance degrades quickly with multiple projects Always configure PostgreSQL before your first real use
Skipping vm.max_map_count Elasticsearch fails to start or crashes under load with cryptic errors Set vm.max_map_count=524288 in /etc/sysctl.conf and run sysctl -p
Running SonarQube as root Elasticsearch refuses to start — it explicitly blocks running as root as a security measure Always use a dedicated non-root system user like sonarqube
Exposing port 9000 publicly SonarQube accessible without HTTPS or proper auth headers; bypasses Nginx protections Bind to 127.0.0.1 in sonar.properties and let Nginx handle external traffic
Not changing the default admin password An internet-facing SonarQube with admin/admin credentials — this gets found quickly by scanners Change the password immediately on first login
Nginx timeout too short Large analysis uploads time out with a 504 from Nginx before SonarQube processes them Set proxy_read_timeout 300; in your Nginx server block

Frequently Asked Questions

How much RAM does SonarQube actually need?

Minimum 4 GB for a small team with a handful of projects. Realistically, 8 GB is where it gets comfortable. Elasticsearch alone wants 2 GB of heap by default, the SonarQube Web process takes another chunk, and the Compute Engine process needs room too. If you're running this on a 2 GB VPS and wondering why it keeps dying, that's your answer.

Can I run SonarQube with Docker instead?

Yes, and the official Docker image is solid. Docker Compose with a PostgreSQL container is a perfectly reasonable setup — especially for smaller teams or staging environments. The tradeoff is that managing persistent volumes, networking, and systemd integration takes more thought. For a long-lived production instance that needs to survive server reboots and be easy to maintain by people who aren't Docker experts, the native install described here is often simpler to debug.

Why won't Elasticsearch start? The logs show bootstrap checks failed.

Almost always one of two things: vm.max_map_count is too low (Step 4), or SonarQube is being run as root (which Elasticsearch refuses). Check /opt/sonarqube/logs/es.log — the error message will tell you exactly which bootstrap check failed. It's usually the first thing in the log after startup.

How do I integrate SonarQube with my CI pipeline?

Generate an analysis token in SonarQube under My Account → Security. Then add the SonarScanner to your CI job — there are official plugins for Jenkins, GitHub Actions, GitLab CI, and most other popular CI tools. The scanner runs analysis locally and pushes the results to your SonarQube server. For GitHub Actions specifically, there's a sonar-scanner action that handles most of the setup for you.

SonarQube is up but the UI is extremely slow. What's going on?

Usually memory pressure. Check if Elasticsearch is being throttled or hitting swap with htop or free -h. If the server is under 8 GB RAM and running other services alongside SonarQube, you may need to tune the JVM heap sizes in /opt/sonarqube/conf/sonar.properties — look for sonar.web.javaOpts and sonar.ce.javaOpts — or move SonarQube to a dedicated server.


Conclusion

Deploying SonarQube from scratch takes a bit of upfront effort, but establishing a robust, natively-hosted instance pays off exponentially. By pairing it with PostgreSQL, setting proper OS limits, running it securely under its own system user, and terminating HTTPS with Nginx, you've created a production-grade setup that can handle serious engineering workloads.

The next step is wiring this into your CI/CD pipeline. Once your pull requests start failing automatically for newly introduced vulnerabilities and code smells, you'll wonder how your team ever shipped software without it. Welcome to the era of automated quality gates.

f X W