SonarQube is a static analysis server that inspects source code for bugs, security vulnerabilities, code smells and duplicated code, and tracks test coverage over time. Its quality gates let your CI pipeline fail when new code does not meet the standard you define. In this tutorial you will install SonarQube Community Build on Ubuntu 24.04 with a PostgreSQL database, run it as a systemd service behind Nginx with a Let's Encrypt certificate, and analyze a first project from the command line and from GitLab CI.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS on x86_64, for example a CubePath VPS, with at least 2 vCPUs and 4 GB of RAM. SonarQube runs three Java processes (web server, compute engine and an embedded Elasticsearch), so 2 GB is not enough in practice.
  • A non-root user with sudo privileges.
  • A domain name such as sonar.your_domain with a DNS A record pointing to your server's public IP address.
  • Ports 80 and 443 reachable from the internet for Let's Encrypt validation and HTTPS.

Throughout the guide, replace sonar.your_domain with your own hostname and your_strong_password with a password you generate.

Step 1 - Preparing the system

SonarQube's embedded Elasticsearch refuses to start unless some kernel limits are raised. Create a sysctl file with the values SonarSource requires:

sudo nano /etc/sysctl.d/99-sonarqube.conf
vm.max_map_count=524288
fs.file-max=131072

Apply the settings and check them:

sudo sysctl --system
sysctl vm.max_map_count
vm.max_map_count = 524288

Install Java 17, unzip and the other tools used in this guide:

sudo apt update
sudo apt install openjdk-17-jre-headless unzip curl
java -version
openjdk version "17.0.x" ...
OpenJDK Runtime Environment (build 17.0.x+...-Ubuntu-...)

Step 2 - Installing PostgreSQL and creating the database

SonarQube needs an external database for anything beyond a quick test. Ubuntu 24.04 ships PostgreSQL 16, which SonarQube supports:

sudo apt install postgresql
sudo systemctl status postgresql

Open a PostgreSQL shell as the postgres superuser:

sudo -u postgres psql

Create a dedicated role and a database owned by it. Replace your_strong_password:

CREATE USER sonarqube WITH ENCRYPTED PASSWORD 'your_strong_password';
CREATE DATABASE sonarqube OWNER sonarqube;
\q

Because sonarqube owns the database, it can create tables in its public schema without additional grants. Test the login over TCP, the same way SonarQube will connect:

psql -h 127.0.0.1 -U sonarqube -d sonarqube -c "SELECT current_user;"
 current_user
--------------
 sonarqube
(1 row)

Step 3 - Downloading SonarQube

Create a system user that will own and run SonarQube. Elasticsearch refuses to run as root, so this user is mandatory:

sudo useradd --system --home-dir /opt/sonarqube --shell /usr/sbin/nologin sonarqube

Check the latest Community Build version on the SonarQube downloads page and set it in a variable. The version below is an example; replace it with the current one:

SONAR_VERSION=25.1.0.102122
curl -fLO "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${SONAR_VERSION}.zip"

Extract the archive to /opt/sonarqube and give ownership to the new user:

sudo unzip -q "sonarqube-${SONAR_VERSION}.zip" -d /opt
sudo mv "/opt/sonarqube-${SONAR_VERSION}" /opt/sonarqube
sudo chown -R sonarqube:sonarqube /opt/sonarqube

Step 4 - Configuring SonarQube

The main configuration file is /opt/sonarqube/conf/sonar.properties. Every setting is present but commented out. Open it:

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

Find and uncomment (or add) the following lines. They point SonarQube to PostgreSQL and make the web server listen only on localhost, because Nginx will handle public traffic:

sonar.jdbc.username=sonarqube
sonar.jdbc.password=your_strong_password
sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqube

sonar.web.host=127.0.0.1
sonar.web.port=9000

The file now contains the database password, so restrict its permissions:

sudo chmod 640 /opt/sonarqube/conf/sonar.properties

Step 5 - Creating a systemd service

Create a unit file so SonarQube starts at boot and runs as the sonarqube user with the file and process limits Elasticsearch needs:

sudo nano /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube
After=network.target postgresql.service

[Service]
Type=forking
User=sonarqube
Group=sonarqube
ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop
LimitNOFILE=131072
LimitNPROC=8192
Restart=on-failure
TimeoutStartSec=120

[Install]
WantedBy=multi-user.target

Load the unit and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now sonarqube

The first start takes one or two minutes while SonarQube creates the database schema. Watch the main log until you see that it is operational:

sudo tail -f /opt/sonarqube/logs/sonar.log
... INFO  app[][o.s.a.SchedulerImpl] Process[web] is up
... INFO  app[][o.s.a.SchedulerImpl] Process[ce] is up
... INFO  app[][o.s.a.SchedulerImpl] SonarQube is operational

Press CTRL+C to stop following the log, then query the status API:

curl -s http://127.0.0.1:9000/api/system/status
{"id":"...","version":"25.1.0.102122","status":"UP"}

Step 6 - Publishing SonarQube with Nginx and HTTPS

Install Nginx and Certbot with its Nginx plugin:

sudo apt install nginx certbot python3-certbot-nginx

Create a server block for SonarQube:

sudo nano /etc/nginx/sites-available/sonarqube
server {
    listen 80;
    listen [::]:80;
    server_name sonar.your_domain;

    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:9000;
        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;
    }
}

Enable the site, test the syntax and reload Nginx:

sudo ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Allow HTTP and HTTPS through the firewall. If UFW is not active yet, allow SSH first so you do not lock yourself out:

sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw enable

Request a certificate. Certbot edits the server block to add TLS and an HTTP to HTTPS redirect:

sudo certbot --nginx -d sonar.your_domain
Successfully deployed certificate for sonar.your_domain to /etc/nginx/sites-enabled/sonarqube
Congratulations! You have successfully enabled HTTPS on https://sonar.your_domain

Certbot also installs a systemd timer that renews the certificate automatically. You can test renewal with sudo certbot renew --dry-run.

Step 7 - Securing the admin account

Open https://sonar.your_domain in your browser. Log in with the default credentials admin / admin. SonarQube forces you to change the password on first login; choose a strong one.

Then set the public URL so links in notifications and pull request decorations are correct: go to Administration > Configuration > General Settings > General and set Server base URL to https://sonar.your_domain.

Step 8 - Analyzing your first project

Create a project and a token for it:

  1. Select Create Project > Local project (the wording can vary slightly between versions).
  2. Enter a display name and a project key, for example my-app, and choose the main branch name.
  3. Choose Locally as the analysis method and generate a project analysis token. Copy it; it is shown only once.

In the root of your code repository, create a sonar-project.properties file:

sonar.projectKey=my-app
sonar.sources=src
sonar.exclusions=**/node_modules/**,**/dist/**

The simplest way to run the scanner is the official Docker image, which bundles the right Java version, so you only need Docker on the machine that runs the analysis. From the repository root, run:

docker run --rm \
  -e SONAR_HOST_URL="https://sonar.your_domain" \
  -e SONAR_TOKEN="your_project_token" \
  -v "$(pwd):/usr/src" \
  sonarsource/sonar-scanner-cli

At the end of the output the scanner prints a link to the results:

INFO  ANALYSIS SUCCESSFUL, you can find the results at: https://sonar.your_domain/dashboard?id=my-app
INFO  EXECUTION SUCCESS

Open the link to see bugs, vulnerabilities, code smells and the quality gate status for the project.

Step 9 - Running the analysis in GitLab CI

To analyze every push and merge request, store the server URL and token as CI/CD variables in GitLab (Settings > CI/CD > Variables): SONAR_HOST_URL and SONAR_TOKEN, with the token marked as Masked. Then add this job to .gitlab-ci.yml:

sonarqube-check:
  stage: test
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - sonar-scanner -Dsonar.qualitygate.wait=true
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

GIT_DEPTH: "0" gives the scanner the full Git history, which it uses to detect new code and assign issues to authors. sonar.qualitygate.wait=true makes the job wait for the server's verdict and fail when the quality gate fails, so a merge request with new critical issues is blocked.

The built-in Sonar way quality gate focuses on new code: it fails when new code has issues above the allowed rating, less than 80% coverage, more than 3% duplication or unreviewed security hotspots. You can copy it under Quality Gates and adjust the conditions to your team's standard.

Step 10 - Backing up SonarQube

All analysis data lives in PostgreSQL, and the configuration lives in /opt/sonarqube/conf. A consistent backup is a database dump plus a copy of that directory:

sudo mkdir -p /var/backups/sonarqube
sudo -u postgres pg_dump -Fc sonarqube | sudo tee /var/backups/sonarqube/sonarqube-$(date +%F).dump > /dev/null
sudo tar -czf /var/backups/sonarqube/sonarqube-conf-$(date +%F).tar.gz -C /opt/sonarqube conf

You do not need to back up /opt/sonarqube/data/es8 (or es7 on older versions): the search index is rebuilt from the database if it is missing.

Troubleshooting

SonarQube stops right after starting and es.log shows max virtual memory areas vm.max_map_count [65530] is too low. The sysctl settings from Step 1 were not applied. Run sudo sysctl --system and check the value again.

can not run elasticsearch as root. SonarQube was started as root, usually by running sonar.sh by hand. Stop it, run sudo chown -R sonarqube:sonarqube /opt/sonarqube to fix any files it created, and start it with sudo systemctl start sonarqube.

The service fails and web.log shows a JDBC or authentication error. Check the sonar.jdbc.* lines in sonar.properties and test the login with the psql -h 127.0.0.1 command from Step 2.

Each process logs to its own file. Look in /opt/sonarqube/logs/: sonar.log (main process), web.log (web server), ce.log (background analysis) and es.log (Elasticsearch).

Conclusion

You have SonarQube Community Build running on Ubuntu 24.04 with PostgreSQL, managed by systemd and published over HTTPS through Nginx, and a GitLab CI job that blocks merge requests that fail the quality gate. Next, you can connect SonarQube to GitLab or GitHub for single sign-on, generate test coverage reports in your pipeline so SonarQube can track coverage, and schedule the backup commands from Step 10 with a systemd timer.