How to Install SonarQube on Ubuntu 24.04 — Self-Hosted Code Quality & Security
Code quality tooling is no longer optional. Teams that ship software without static analysis accumulate technical debt, security vulnerabilities, and maintenance headaches at a rate that compounds with every release. This guide walks you through installing SonarQube 10 Community on an Ubuntu 24.04 VPS — the de-facto open-source platform for continuous inspection of code quality and security across more than 20 programming languages.
By the end of this tutorial, you will have a production-ready SonarQube instance running behind Nginx with TLS, backed by PostgreSQL 16, scanning your first Node, Java, or Python project with Quality Gates enforced in CI.
Table of Contents
What is SonarQube?
SonarQube is an open-source platform for continuous inspection of source code. It scans your repositories and reports on three major dimensions: bugs (reliability issues that will crash or misbehave at runtime), vulnerabilities (security weaknesses such as SQL injection, hardcoded secrets, XSS, and OWASP Top 10 patterns), and code smells (maintainability problems like duplicated code, excessive complexity, and dead code).
The official SonarQube documentation at docs.sonarqube.org describes the platform as a full-stack analyzer covering 20+ languages: Java, JavaScript, TypeScript, Python, C#, C, C++, Go, Kotlin, Swift, Ruby, PHP, Scala, Dart, HTML, CSS, plus infrastructure-as-code languages like Terraform, CloudFormation, Kubernetes manifests, and Dockerfiles. On top of bug and vulnerability detection, SonarQube tracks test coverage, duplicated code blocks, cyclomatic complexity, and technical debt (expressed in hours of estimated remediation effort).
SonarQube is commonly used by development teams to:
- Block pull requests that introduce new bugs, vulnerabilities, or coverage regressions
- Enforce organization-wide coding standards via shared Quality Profiles
- Track technical debt trends across months and releases
- Detect hardcoded secrets before code reaches production
- Measure code quality per developer, per sprint, per application
Why Self-Host vs. SonarCloud
SonarSource offers a hosted SaaS version called SonarCloud. It is convenient, but for teams with private code, self-hosting SonarQube Community on a VPS is almost always the better choice. Here is why:
- Unlimited lines of code at zero marginal cost — SonarCloud charges per line of code for private projects (hundreds of euros per month for a mid-size codebase). Community Edition on your own VPS analyzes millions of lines at a flat hosting cost.
- Your source code never leaves your infrastructure — SonarQube's scanner sends raw source files to the server during analysis. Keeping that on a VPS you control eliminates any data-residency or NDA concerns.
- Full plugin freedom — You can install community plugins, write your own custom rules, and integrate with any CI/CD tool. SonarCloud restricts plugins to a short vetted list.
- No vendor retention limits — Self-hosted instances keep history indefinitely. SonarCloud retention is tied to your subscription.
- Predictable monthly cost — A single CloudCore Professional VPS at EUR 19.99 per month comfortably runs SonarQube plus PostgreSQL for a team of 20 developers. That is cheaper than a single SonarCloud user-month at many tiers.
- Works for air-gapped environments — Government, finance, and regulated industries often cannot send source code to a public SaaS. Self-hosting is the only path.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- A domain name (e.g.
sonar.example.com) with an A record pointing at your VPS IP - At least 4 GB of RAM (8 GB recommended for teams of 5+)
- At least 30 GB of free disk space (SonarQube plus PostgreSQL grow with analysis history)
- Ports 80, 443, and 22 open in your firewall
Recommended Plan: CloudCore Professional — EUR 19.99/month>
SonarQube bundles Elasticsearch, which is memory-hungry. The CloudCore Professional plan gives you:>
- 6 vCPU cores
- 12 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth
- EUR 19.99/month>
This is enough to run SonarQube, PostgreSQL 16, Nginx, and your CI runner on the same box with plenty of headroom.
Connect via SSH to get started:
ssh root@your-server-ipStep 1: Update the System and Tune the Kernel
Start by updating your package index and applying outstanding security patches:
sudo apt update && sudo apt upgrade -ySonarQube's embedded Elasticsearch requires several kernel parameters to be raised above the Ubuntu defaults. Without these settings, Elasticsearch will refuse to boot and you will see max virtual memory areas vm.max_map_count [65530] is too low in the logs.
Create a sysctl drop-in file:
sudo tee /etc/sysctl.d/99-sonarqube.conf > /dev/null <<EOF
vm.max_map_count=524288
fs.file-max=131072
EOFApply the settings immediately:
sudo sysctl --systemVerify:
sysctl vm.max_map_count fs.file-maxExpected output:
vm.max_map_count = 524288
fs.file-max = 131072Next raise the open-file and max-process limits for the sonar user (which you will create in Step 4):
sudo tee /etc/security/limits.d/99-sonarqube.conf > /dev/null <<EOF
sonar - nofile 131072
sonar - nproc 8192
EOFThese limits are essential — SonarQube routinely opens tens of thousands of file descriptors during heavy analysis and will abort with Elasticsearch did not start in time errors if they are too low.
Step 2: Install PostgreSQL 16
SonarQube 10 supports PostgreSQL 13 or later and explicitly drops support for MySQL, MSSQL, and Oracle in newer releases. PostgreSQL 16 from the official PGDG repo is the recommended choice on Ubuntu 24.04.
Install the PostgreSQL apt repository:
sudo apt install -y curl ca-certificates gnupg lsb-release
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
sudo gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg
echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] \
https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | \
sudo tee /etc/apt/sources.list.d/pgdg.listInstall PostgreSQL 16:
sudo apt update
sudo apt install -y postgresql-16 postgresql-contrib-16Confirm the service is running:
sudo systemctl status postgresql@16-mainCreate the sonar database and user:
sudo -u postgres psql <<'EOF'
CREATE USER sonar WITH ENCRYPTED PASSWORD 'ChangeMeToStrongPassword!';
CREATE DATABASE sonarqube OWNER sonar ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO sonar;
ALTER USER sonar CREATEDB;
EOFReplace ChangeMeToStrongPassword! with a long random password (you can generate one with openssl rand -base64 32).
Test the new credentials:
psql -h 127.0.0.1 -U sonar -d sonarqube -c '\conninfo'For more database tuning tips, see our PostgreSQL install guide.
Step 3: Install OpenJDK 17
SonarQube 10 requires Java 17. Ubuntu 24.04 ships OpenJDK 17 in its default repositories:
sudo apt install -y openjdk-17-jdk-headlessVerify:
java -versionExpected output:
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-124.04, mixed mode, sharing)Set JAVA_HOME globally so scanners and the SonarQube service can locate the JDK:
sudo tee /etc/profile.d/java.sh > /dev/null <<'EOF'
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$PATH:$JAVA_HOME/bin
EOF
source /etc/profile.d/java.shStep 4: Download and Install SonarQube 10
SonarQube should run as an unprivileged system user — never as root. Create the sonar user:
sudo useradd -r -m -d /opt/sonarqube -s /bin/bash sonarDownload the latest SonarQube 10 Community build. Check binaries.sonarsource.com for the current version; at the time of writing it is 10.7.0.96327:
cd /tmp
SONAR_VERSION="10.7.0.96327"
sudo apt install -y unzip
wget "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${SONAR_VERSION}.zip"
sudo unzip "sonarqube-${SONAR_VERSION}.zip" -d /opt
sudo mv "/opt/sonarqube-${SONAR_VERSION}" /opt/sonarqubeSet ownership so the sonar user can read, write, and execute inside the install directory:
sudo chown -R sonar:sonar /opt/sonarqubeThe resulting layout:
/opt/sonarqube/
├── bin/ (startup scripts for each platform)
├── conf/ (sonar.properties, wrapper.conf)
├── data/ (Elasticsearch index, embedded H2 dev DB)
├── elasticsearch/ (bundled Elasticsearch)
├── extensions/ (plugins directory)
├── lib/ (SonarQube JARs)
├── logs/ (sonar.log, web.log, ce.log, es.log)
├── temp/
└── web/ (frontend assets)Step 5: Configure sonar.properties
Edit the main configuration file to point SonarQube at PostgreSQL and bind it to localhost (Nginx will proxy external traffic):
sudo -u sonar nano /opt/sonarqube/conf/sonar.propertiesUncomment and edit the following lines (they are scattered through the file — use search to locate them):
# JDBC (PostgreSQL)
sonar.jdbc.username=sonar
sonar.jdbc.password=ChangeMeToStrongPassword!
sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqubeWeb server
sonar.web.host=127.0.0.1
sonar.web.port=9000
sonar.web.context=/Embedded database is ONLY for evaluation — make sure the JDBC URL above is set.
sonar.embeddedDatabase.port=9092
Elasticsearch (keep defaults unless you have a conflict)
sonar.search.host=127.0.0.1
sonar.search.port=9001Save and exit (Ctrl+O, Enter, Ctrl+X).
The important points:
sonar.web.host=127.0.0.1— Do not expose port 9000 to the public internet directly. Nginx will handle TLS and the external listener.- JDBC URL — Matches the database you created in Step 2. If your PostgreSQL is on another server, replace
localhostwith its hostname and make surepg_hba.confallows the connection. - Elasticsearch ports — Defaults (9001) are fine for a single-node install. Only change them if another service already occupies the port.
Step 6: Create the systemd Service
Rather than using the bundled shell scripts, wrap SonarQube in a proper systemd unit so it starts on boot and restarts on failure:
sudo tee /etc/systemd/system/sonarqube.service > /dev/null <<'EOF' [Unit] Description=SonarQube service After=syslog.target network.target 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=sonar Group=sonar Restart=on-failure LimitNOFILE=131072 LimitNPROC=8192 TimeoutStartSec=5min
[Install] WantedBy=multi-user.target EOF
Reload systemd and enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now sonarqubeInitial startup takes 2-5 minutes while Elasticsearch bootstraps and SonarQube migrates the database schema. Watch the logs:
sudo tail -f /opt/sonarqube/logs/sonar.logLook for the line:
INFO app[][o.s.a.SchedulerImpl] SonarQube is operationalConfirm the service is healthy:
sudo systemctl status sonarqube
curl -sS http://127.0.0.1:9000/api/system/statusExpected output:
{"id":"...","version":"10.7.0.96327","status":"UP"}Step 7: Nginx Reverse Proxy with TLS
Serving SonarQube directly on port 9000 over HTTP is a non-starter for production — you need TLS, sensible timeouts, and an upgrade path for future services.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the Nginx site file:
sudo tee /etc/nginx/sites-available/sonarqube > /dev/null <<'EOF' server { listen 80; server_name sonar.example.com; return 301 https://$host$request_uri; }server { listen 443 ssl http2; server_name sonar.example.com;
# Placeholder certificate paths — Certbot will overwrite these ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
# Uploads can be large when scanning big codebases client_max_body_size 100M;
# SonarQube uses long-polling for background tasks proxy_read_timeout 600s; proxy_send_timeout 600s;
# Security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Referrer-Policy strict-origin-when-cross-origin;
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_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } EOF
Replace sonar.example.com with your actual DNS name. Enable the site and obtain a certificate:
sudo ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d sonar.example.com --redirect --agree-tos -m [email protected]Certbot replaces the snakeoil certificate paths with real Let's Encrypt certificates and sets up automatic renewal via a systemd timer.
Visit https://sonar.example.com in a browser — you should see the SonarQube login page.
Step 8: First Login and Create a Project
Default credentials on a fresh install are:
- Username:
admin - Password:
admin
Once inside:
my-first-appmy-first-app (auto-generated, keep or customise)mainOn the "How do you want to analyze your repository?" screen, pick Locally for your first scan. SonarQube will prompt you to generate an analysis token — give it a name (my-first-app-token) and expiration (90 days is a sensible default) and copy the resulting token somewhere safe. You will paste it into the scanner command.
Step 9: Install sonar-scanner CLI
The scanner is a separate command-line tool that you run on your developer machine or CI runner — it uploads analysis results to the SonarQube server. It is not the same as the SonarQube server itself.
Install on Ubuntu:
cd /opt
SCANNER_VERSION="6.2.1.4610"
sudo wget "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip"
sudo unzip "sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip"
sudo mv "sonar-scanner-${SCANNER_VERSION}-linux-x64" /opt/sonar-scanner
sudo ln -s /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scannerVerify:
sonar-scanner --versionEdit the default global config to point at your server:
sudo nano /opt/sonar-scanner/conf/sonar-scanner.propertiesSet:
sonar.host.url=https://sonar.example.com
sonar.scanner.javaOpts=-Xmx1024mStep 10: Scan Your First Project
In the root of the project you want to analyze, create a sonar-project.properties file. The exact content depends on the language.
Node.js / TypeScript
sonar.projectKey=my-first-app
sonar.projectName=My First App
sonar.projectVersion=1.0
sonar.sources=src
sonar.exclusions=/node_modules/,/dist/,*/.test.ts
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.typescript.tsconfigPath=tsconfig.jsonRun the scan:
sonar-scanner -Dsonar.token=YOUR_TOKEN_HEREJava (Maven)
Maven projects use the sonar-maven-plugin instead of the CLI:
mvn clean verify sonar:sonar \
-Dsonar.host.url=https://sonar.example.com \
-Dsonar.token=YOUR_TOKEN_HERE \
-Dsonar.projectKey=my-first-appPython
sonar.projectKey=my-first-app
sonar.projectName=My First App
sonar.sources=.
sonar.exclusions=/venv/,/__pycache__/,/tests/
sonar.python.coverage.reportPaths=coverage.xml
sonar.python.version=3.12Generate coverage with pytest --cov --cov-report=xml, then:
sonar-scanner -Dsonar.token=YOUR_TOKEN_HEREWhichever language you use, watch the console output — the scanner uploads results and finishes with a URL like:
ANALYSIS SUCCESSFUL, you can find the results at: https://sonar.example.com/dashboard?id=my-first-appOpen that URL to see bugs, vulnerabilities, code smells, duplicated lines, and coverage, all broken down by file.
Step 11: Quality Gates and Branch/PR Analysis
A Quality Gate is a set of boolean conditions that determine whether a scan passes or fails. The built-in Sonar way gate enforces:
- 0 new bugs on new code
- 0 new vulnerabilities on new code
- 0 new security hotspots reviewed as "To Review"
- Coverage on new code ≥ 80%
- Duplicated lines on new code ≤ 3%
- Maintainability rating on new code = A
- Reliability rating on new code = A
- Security rating on new code = A
In CI, the scanner exits with code 0 regardless of the gate result by default. To fail the build on a failed gate, append:
sonar-scanner -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300The scanner polls the server for up to 5 minutes and returns non-zero if the gate fails. Wire this into Jenkins, Drone CI, or your self-hosted Gitea Actions runner so that bad scans block merges.
Step 12: GitHub Integration
Note: Full inline PR comments require Developer Edition or higher. Community Build posts results only inside SonarQube — not as GitHub review comments. The steps below show how to configure the CI-side scan that feeds data back to SonarQube; inline decoration is a paid feature.
Create a GitHub Actions workflow in your repository at .github/workflows/sonar.yml:
name: SonarQube Scan on: push: branches: [main] pull_request: types: [opened, synchronize, reopened]
jobs: sonarqube: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: sonarsource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: https://sonar.example.com - uses: sonarsource/sonarqube-quality-gate-action@master timeout-minutes: 5 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Add your SonarQube token and host URL as GitHub repository secrets (Settings → Secrets and variables → Actions).
Every push and pull request now runs a full analysis, and the workflow fails if the Quality Gate fails. Upgrade to Developer Edition later if you want checkmark/X annotations appearing directly in the GitHub PR "Files changed" tab.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
max virtual memory areas vm.max_map_count [65530] is too low | Kernel limits not raised | Re-run sudo sysctl --system after Step 1 |
Elasticsearch did not start in time | File descriptor limits too low | Confirm /etc/security/limits.d/99-sonarqube.conf is in place and reboot |
FATAL: password authentication failed for user "sonar" | JDBC password mismatch | Compare sonar.jdbc.password in sonar.properties with the PostgreSQL role password |
SonarQube stuck on STARTING status | PostgreSQL unreachable or JDK wrong version | Check logs/web.log, confirm Java 17, test psql -h 127.0.0.1 -U sonar sonarqube |
| 502 Bad Gateway from Nginx | SonarQube not listening on 127.0.0.1:9000 yet | Wait for startup to complete (up to 5 min on first boot), check curl http://127.0.0.1:9000 |
Scanner error: You're not authorized | Token missing or expired | Regenerate token in Account → Security and pass with -Dsonar.token= |
| Coverage shows 0% | Coverage report path wrong | Verify sonar.javascript.lcov.reportPaths / sonar.python.coverage.reportPaths points to an existing file |
Viewing Logs
SonarQube splits its logs by process — always check the right one:
/opt/sonarqube/logs/sonar.log— Main app and wrapper/opt/sonarqube/logs/web.log— Web server errors/opt/sonarqube/logs/ce.log— Compute engine (background analysis processing)/opt/sonarqube/logs/es.log— Embedded Elasticsearch
sudo tail -f /opt/sonarqube/logs/*.logFAQ
Why self-host SonarQube instead of using SonarCloud?
Self-hosting SonarQube Community gives you unlimited lines of code analysis for free, keeps proprietary source code inside your own network, and gives you full control over retention, integrations, and plugin extensions. SonarCloud is billed per line of code for private projects, whereas a self-hosted instance on a VPS has a predictable flat monthly cost.
What are the minimum server requirements for SonarQube 10?
SonarQube 10 requires a minimum of 3 GB of free RAM for the Elasticsearch search engine plus the web and compute engine processes. For production use and team sizes of 5 to 20 developers, plan for 4 vCPU, 8 GB RAM, and at least 50 GB of disk. A CloudCore Professional plan with 6 vCPU and 12 GB RAM at EUR 19.99/month handles this comfortably.
Why is vm.max_map_count important for SonarQube?
SonarQube embeds Elasticsearch, which requires a large number of memory-mapped areas. The Linux default of 65530 is too low and Elasticsearch will fail to start. Setting vm.max_map_count=524288 and fs.file-max=131072 is required for SonarQube to boot on any Linux distribution.
Does SonarQube Community support pull request decoration?
Pull request decoration for GitHub, GitLab, Bitbucket, and Azure DevOps is available starting with Developer Edition. The Community Build edition analyzes pull requests via CI but posts results only inside SonarQube. For GitHub PR comments with inline annotations you need Developer Edition or higher.
Which languages does SonarQube Community analyze?
The free Community edition analyzes over 20 languages including Java, JavaScript, TypeScript, Python, C#, Go, Kotlin, Ruby, PHP, HTML, CSS, and infrastructure-as-code languages like Terraform, CloudFormation, Kubernetes YAML, and Dockerfiles.
How do Quality Gates work?
A Quality Gate is a set of conditions that a project must meet before it is considered production-ready. The built-in Sonar way gate enforces zero new bugs, zero new vulnerabilities, new code coverage above 80 percent, and duplicated lines below 3 percent. When a scan fails the gate, CI pipelines can be set to fail automatically using -Dsonar.qualitygate.wait=true.
Can SonarQube run in Docker instead of on bare metal?
Yes, official Docker images are available. However for a production deployment on a dedicated VPS, the bare-metal install described in this guide gives you simpler log access, faster startup, easier backup of PostgreSQL, and lower overhead. Docker is a good fit when you already run a container orchestrator.
Next Steps
Now that SonarQube is running, here are logical follow-ups:
- Wire SonarQube into your CI — If you already run Jenkins or Drone CI, add a SonarQube scan stage so every commit is analyzed automatically.
- Host your source code self-contained — Pair SonarQube with a self-hosted Gitea instance for a complete, fully-private dev platform.
- Back up the PostgreSQL database — SonarQube keeps all issue history and analysis metadata in PostgreSQL. Set up daily
pg_dumpto offsite storage. See our PostgreSQL guide for backup patterns. - Add custom Quality Profiles — Clone
Sonar way, disable rules that do not match your team's style, and enable language-specific extras likecognitive-complexityandsecurity-hotspots. - Install language plugins — Drop additional analyzers into
/opt/sonarqube/extensions/plugins/to cover niche languages like Dart, Rust (via community plugin), or Terraform. - Enable LDAP or SAML SSO — For teams, wire SonarQube into your existing identity provider instead of managing local accounts.
Ready to run SonarQube in production?>
Spin up a CloudCore Professional VPS with 6 vCPU, 12 GB RAM, and 100 GB NVMe for EUR 19.99/month — more than enough for SonarQube, PostgreSQL 16, and your CI runner on one box. Deploy in under 60 seconds and start scanning your code today.