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.
7) Install & configure Nginx as a reverse proxy (optional but recommended)
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
andcurl -I http://127.0.0.1:9000
on the server.Secure with HTTPS: add Let’s Encrypt (Certbot) to the Nginx server block.
Subscribe to my newsletter
Read articles from Shaharyar Shakir directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
