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
sudoprivileges. - A domain name such as
sonar.your_domainwith 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:
- Select Create Project > Local project (the wording can vary slightly between versions).
- Enter a display name and a project key, for example
my-app, and choose the main branch name. - 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.