Day 72 — Setting up SonarQube

Today I stood up a production-style SonarQube 9.9 LTS stack on an AWS EC2 Ubuntu instance, backed by PostgreSQL and fronted by Nginx. Below is the exact flow (with reasons for every command) based on the script I used.

  • Recommended EC2: t3.medium (2 vCPU / 4 GB RAM) or bigger(t2.medium/t2.small).

  • Open security-group ports: 22 (SSH), 80 (HTTP). (Port 9000 optional if you’ll access SonarQube directly without Nginx.)


1) Kernel & limits tuning (required by Elasticsearch inside SonarQube)

SonarQube bundles Elasticsearch, which needs higher map counts and file handles.

# Back up, then set VM and file limits (map count is critical for Elasticsearch)
cp /etc/sysctl.conf /root/sysctl.conf_backup
cat <<EOT> /etc/sysctl.conf
vm.max_map_count=262144
fs.file-max=65536
ulimit -n 65536
ulimit -u 4096
EOT

Why:

  • vm.max_map_count=262144 → mandatory for Elasticsearch.

  • fs.file-max=65536 → raises system file descriptors.

  • Note: ulimit is a shell setting (not read by sysctl). We additionally set user limits below and in the systemd unit, which is what actually takes effect for the service.

Set per-user limits for the sonarqube service account:

cp /etc/security/limits.conf /root/sec_limit.conf_backup
cat <<EOT> /etc/security/limits.conf
sonarqube   -   nofile   65536
sonarqube   -   nproc    409
EOT

Why: Ensures the SonarQube process can open enough files and processes.

Changes to sysctl.conf will apply after reboot (the script reboots at the end).
Alternatively: sudo sysctl --system.


2) Install Java 17 (required)

sudo apt-get update -y
sudo apt-get install openjdk-17-jdk -y
sudo update-alternatives --config java
java -version

Why: SonarQube 9.9 LTS requires Java 17. We verify the active Java version.


3) Install & configure PostgreSQL

Add the official PostgreSQL APT repo and install:

sudo apt update
wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
sudo apt install postgresql postgresql-contrib -y
sudo systemctl enable postgresql.service
sudo systemctl start  postgresql.service

Secure and create DB/user for SonarQube:

# Set postgres user password (optional, for local admin use)
sudo echo "postgres:admin123" | chpasswd

# Create DB user + database for SonarQube
runuser -l postgres -c "createuser sonar"
sudo -i -u postgres psql -c "ALTER USER sonar WITH ENCRYPTED PASSWORD 'admin123';"
sudo -i -u postgres psql -c "CREATE DATABASE sonarqube OWNER sonar;"
sudo -i -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE sonarqube to sonar;"
systemctl restart  postgresql

# (Optional) confirm Postgres is listening
netstat -tulpena | grep postgres

Why: SonarQube needs a production-grade RDBMS. We create a dedicated sonar user and sonarqube database.


4) Download & lay out SonarQube

sudo mkdir -p /sonarqube/
cd /sonarqube/
sudo curl -O https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-9.9.8.100196.zip
sudo apt-get install zip -y
sudo unzip -o sonarqube-9.9.8.100196.zip -d /opt/
sudo mv /opt/sonarqube-9.9.8.100196/ /opt/sonarqube

Create a dedicated service account and set ownership:

sudo groupadd sonar
sudo useradd -c "SonarQube - User" -d /opt/sonarqube/ -g sonar sonar
sudo chown sonar:sonar /opt/sonarqube/ -R

Why: Keeps the service isolated and secure.


5) Configure SonarQube to use PostgreSQL & listen on 9000

cp /opt/sonarqube/conf/sonar.properties /root/sonar.properties_backup
cat <<EOT> /opt/sonarqube/conf/sonar.properties
sonar.jdbc.username=sonar
sonar.jdbc.password=admin123
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube

sonar.web.host=0.0.0.0
sonar.web.port=9000

sonar.web.javaAdditionalOpts=-server
sonar.search.javaOpts=-Xmx512m -Xms512m -XX:+HeapDumpOnOutOfMemoryError
sonar.log.level=INFO
sonar.path.logs=logs
EOT

Why: Points SonarQube at the local PostgreSQL database and exposes the web UI on port 9000.


6) Create a systemd service for SonarQube

cat <<EOT> /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube service
After=syslog.target network.target

[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=always
LimitNOFILE=65536
LimitNPROC=4096

[Install]
WantedBy=multi-user.target
EOT

systemctl daemon-reload
systemctl enable sonarqube.service
# Start happens after reboot in this script; to start now:
# systemctl start sonarqube.service
# journalctl -u sonarqube -f

Why: Lets Ubuntu manage SonarQube as a service with correct limits.


apt-get install nginx -y
rm -rf /etc/nginx/sites-enabled/default
rm -rf /etc/nginx/sites-available/default

cat <<EOT> /etc/nginx/sites-available/sonarqube
server{
    listen      80;
    server_name sonarqube.groophy.in;

    access_log  /var/log/nginx/sonar.access.log;
    error_log   /var/log/nginx/sonar.error.log;

    proxy_buffers 16 64k;
    proxy_buffer_size 128k;

    location / {
        proxy_pass  http://127.0.0.1:9000;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect off;

        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 http;
    }
}
EOT

ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/sonarqube
systemctl enable nginx.service
# systemctl restart nginx.service

Why:

  • Serves SonarQube via standard HTTP/80 (and later easy HTTPS with Let’s Encrypt).

  • Replace sonarqube.groophy.in with your domain.

  • If you don’t use Nginx, connect directly to http://<EC2>:9000.


8) Firewall rules (UFW on Ubuntu)

sudo ufw allow 80,9000,9001/tcp

Why: Opens HTTP (80) and SonarQube (9000). Port 9001 is used internally by Elasticsearch; you typically don’t need it publicly, but the rule is included for compatibility. Configure security groups accordingly.


9) Reboot to apply kernel changes

echo "System reboot in 30 sec"
sleep 30
reboot

Why: Ensures vm.max_map_count and other kernel settings are applied, and enabled services (SonarQube, Nginx, PostgreSQL) come up on boot.


10) Verify & first login

  • Browse: http://your-domain/ (via Nginx) or http://<EC2-Public-IP>:9000

  • Default credentials: admin / admin (you’ll be prompted to change the password)

  • Logs (helpful if the UI isn’t up yet):

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

Troubleshooting quick hits

  • Service not up? journalctl -u sonarqube -n 200 --no-pager

  • Elasticsearch error about vm.max_map_count? Confirm sysctl vm.max_map_count returns 262144, then reboot.

  • Reverse proxy 502? systemctl status nginx and curl -I http://127.0.0.1:9000 on the server.

  • Secure with HTTPS: add Let’s Encrypt (Certbot) to the Nginx server block.


0
Subscribe to my newsletter

Read articles from Shaharyar Shakir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shaharyar Shakir
Shaharyar Shakir