How to Install and Configure SonarQube on Windows Server 2025
SonarQube is the leading open-source platform for continuous code quality inspection, detecting bugs, code smells, and security vulnerabilities across more than 30 programming languages. Running SonarQube on Windows Server 2025 as a persistent service allows development teams to integrate static analysis into every pull request and pipeline run, enforcing quality gates that block merges when critical issues are introduced. This guide covers the full SonarQube Community Edition installation: Java prerequisites, PostgreSQL database setup, configuring sonar.properties, installing SonarQube as a Windows service using the bundled wrapper, accessing the web UI, running a first analysis using SonarScanner for .NET, and configuring quality gates to enforce code standards automatically.
Prerequisites
- Windows Server 2025 with at least 4 GB RAM (8 GB recommended; SonarQube’s Elasticsearch component requires significant heap)
- Java 17 (LTS) — SonarQube 10.x requires exactly Java 17; OpenJDK or Eclipse Temurin both work
- PostgreSQL 13–16 for production (the embedded H2 database is for evaluation only and does not support upgrades)
- At least 20 GB free disk space for the SonarQube application and Elasticsearch indices
- A dedicated Windows service account with local administrator rights on the SonarQube server
- .NET 8 SDK on developer workstations or build agents for SonarScanner for .NET
- SonarQube zip archive downloaded from SonarSource
Step 1: Install Java 17
SonarQube 10.x requires Java 17. Eclipse Temurin (formerly AdoptOpenJDK) is a widely-used, production-ready distribution.
# Install Java 17 via winget (Eclipse Temurin)
winget install EclipseAdoptium.Temurin.17.JDK --silent --accept-source-agreements
# Alternatively, download and install manually:
$javaUrl = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.11%2B9/OpenJDK17U-jdk_x64_windows_hotspot_17.0.11_9.msi"
Invoke-WebRequest -Uri $javaUrl -OutFile "C:Temptemurin17.msi" -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i C:Temptemurin17.msi /quiet /norestart ADDLOCAL=FeatureMain,FeatureEnvironment,FeatureJarFileRunWith,FeatureJavaHome" -Wait
# Set JAVA_HOME and verify
$env:JAVA_HOME = "C:Program FilesEclipse Adoptiumjdk-17.0.11.9-hotspot"
[System.Environment]::SetEnvironmentVariable("JAVA_HOME", $env:JAVA_HOME, "Machine")
# Refresh PATH and verify
refreshenv
java -version
Write-Host "JAVA_HOME: $env:JAVA_HOME"
Step 2: Install and Configure PostgreSQL
SonarQube requires a dedicated database and user in PostgreSQL. Use UTF-8 encoding; other encodings are not supported.
# Install PostgreSQL 16 via winget
winget install PostgreSQL.PostgreSQL.16 --silent --accept-source-agreements
# Add psql to PATH
$pgBin = "C:Program FilesPostgreSQL16bin"
$currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine")
[System.Environment]::SetEnvironmentVariable("PATH", "$currentPath;$pgBin", "Machine")
$env:PATH = "$env:PATH;$pgBin"
# Connect as the postgres superuser and create the SonarQube database and user
$createDbScript = @"
CREATE USER sonarqube WITH PASSWORD 'S0narQube_DB_2025!';
CREATE DATABASE sonarqube
WITH OWNER = sonarqube
ENCODING = 'UTF8'
LC_COLLATE = 'en-US'
LC_CTYPE = 'en-US'
TEMPLATE = template0;
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO sonarqube;
"@
$createDbScript | Out-File -FilePath "C:Tempcreate_sonar_db.sql" -Encoding UTF8
& psql -U postgres -f "C:Tempcreate_sonar_db.sql"
# Verify the database was created
& psql -U postgres -c "l" | Select-String "sonarqube"
Write-Host "PostgreSQL database and user created."
Step 3: Download and Extract SonarQube
# Create installation directory
New-Item -Path "C:SonarQube" -ItemType Directory -Force
# Download SonarQube Community Edition (adjust version as needed)
$sonarVersion = "10.6.0.92116"
$sonarUrl = "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${sonarVersion}.zip"
Write-Host "Downloading SonarQube $sonarVersion..."
Invoke-WebRequest -Uri $sonarUrl -OutFile "C:Tempsonarqube.zip" -UseBasicParsing
# Extract
Expand-Archive -Path "C:Tempsonarqube.zip" -DestinationPath "C:SonarQube" -Force
# Rename the extracted directory for easier referencing
$extractedDir = Get-ChildItem "C:SonarQube" -Directory | Where-Object { $_.Name -like "sonarqube-*" } | Select-Object -First 1
Rename-Item -Path $extractedDir.FullName -NewName "current"
Write-Host "SonarQube extracted to C:SonarQubecurrent"
Get-ChildItem "C:SonarQubecurrent" | Select-Object Name
Step 4: Configure sonar.properties
The main configuration file at C:SonarQubecurrentconfsonar.properties controls database connection, network binding, and JVM memory settings. Edit only the properties you need — leave everything else commented out to use the defaults.
$sonarProps = "C:SonarQubecurrentconfsonar.properties"
# Read and update the properties file
$content = Get-Content $sonarProps
# Function to set a property (uncomment and set value, or append)
function Set-SonarProperty {
param($Content, $Key, $Value)
$pattern = "^#?s*${Key}s*=.*$"
$replacement = "${Key}=${Value}"
if ($Content -match $pattern) {
return $Content -replace $pattern, $replacement
} else {
return $Content + "`n${Key}=${Value}"
}
}
# Database connection
$content = Set-SonarProperty $content "sonar.jdbc.url" "jdbc:postgresql://localhost/sonarqube"
$content = Set-SonarProperty $content "sonar.jdbc.username" "sonarqube"
$content = Set-SonarProperty $content "sonar.jdbc.password" "S0narQube_DB_2025!"
# Web server binding (0.0.0.0 = all interfaces, or specify IP for single-homed)
$content = Set-SonarProperty $content "sonar.web.host" "0.0.0.0"
$content = Set-SonarProperty $content "sonar.web.port" "9000"
# Optional: context path if hosting behind a reverse proxy at a sub-path
# $content = Set-SonarProperty $content "sonar.web.context" "/sonarqube"
# JVM options for the web server (Xms and Xmx must match)
$content = Set-SonarProperty $content "sonar.web.javaOpts" "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"
# JVM options for the Compute Engine (analysis processing)
$content = Set-SonarProperty $content "sonar.ce.javaOpts" "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"
# Elasticsearch JVM options (must have at least 512m)
$content = Set-SonarProperty $content "sonar.search.javaOpts" "-Xmx512m -Xms512m -XX:MaxDirectMemorySize=256m -XX:+HeapDumpOnOutOfMemoryError"
# Write configuration back
$content | Set-Content $sonarProps -Encoding UTF8
Write-Host "sonar.properties configured."
Step 5: Install SonarQube as a Windows Service
SonarQube ships with a wrapper executable that registers it as a Windows service. Run the wrapper from the appropriate architecture directory.
# Navigate to the Windows service wrapper directory
Set-Location "C:SonarQubecurrentbinwindows-x86-64"
# Install the SonarQube service
.InstallNTService.bat
# The above batch file calls the wrapper.exe to register the service.
# You can also register it manually via the wrapper executable:
& "C:SonarQubecurrentbinwindows-x86-64wrapper.exe" `
-i "C:SonarQubecurrentconfwrapper.conf"
# Configure the service for automatic startup
Set-Service -Name "SonarQube" -StartupType Automatic
# Start the SonarQube service
Start-Service -Name "SonarQube"
# Verify service status
Get-Service -Name "SonarQube" | Select-Object Name, Status, StartType
# Monitor startup progress — SonarQube can take 2-3 minutes to start
# Check the web server log:
Get-Content "C:SonarQubecurrentlogsweb.log" -Tail 20 -Wait
SonarQube is ready when you see SonarQube is up in the web.log. If the service fails to start, check C:SonarQubecurrentlogssonar.log and es.log (Elasticsearch) for specific error messages.
Step 6: Access the SonarQube Web UI and Initial Setup
# Open SonarQube in the default browser
Start-Process "http://localhost:9000"
# Default credentials on first login:
# Username: admin
# Password: admin
# You will be prompted to change the password immediately.
# If SonarQube is on a remote server, open the firewall port:
New-NetFirewallRule -DisplayName "SonarQube Web UI" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 9000 `
-Action Allow `
-Profile Domain, Private
Write-Host "Firewall rule created for port 9000."
# Verify SonarQube is responding:
$response = Invoke-WebRequest -Uri "http://localhost:9000/api/system/status" -UseBasicParsing
($response.Content | ConvertFrom-Json).status
Step 7: Run a First Analysis with SonarScanner for .NET
SonarScanner for .NET is a .NET global tool that wraps the standard SonarScanner and integrates with MSBuild and the .NET CLI build system. Install it on your developer machine or CI agent.
# Install SonarScanner for .NET as a global tool
dotnet tool install --global dotnet-sonarscanner
# Or update if already installed:
dotnet tool update --global dotnet-sonarscanner
# Verify installation
dotnet sonarscanner --version
# Generate a project token in SonarQube UI:
# Administration > Security > Users > [your user] > Tokens > Generate
# Or use the API:
$adminBase64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("admin:YourNewAdminPassword!"))
$tokenResponse = Invoke-RestMethod `
-Uri "http://localhost:9000/api/user_tokens/generate" `
-Method POST `
-Headers @{ Authorization = "Basic $adminBase64"; "Content-Type" = "application/x-www-form-urlencoded" } `
-Body "name=MyProjectToken&type=PROJECT_ANALYSIS_TOKEN&projectKey=my-dotnet-app"
$projectToken = $tokenResponse.token
Write-Host "Project token: $projectToken"
# Run analysis — three-step process for .NET projects:
Set-Location "C:SourceMyDotNetApp"
# Step 1: Begin analysis (instruments the build)
dotnet sonarscanner begin `
/k:"my-dotnet-app" `
/d:sonar.host.url="http://localhost:9000" `
/d:sonar.token="$projectToken" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" `
/d:sonar.cs.vstest.reportsPaths="**/*.trx"
# Step 2: Build the project normally
dotnet build --configuration Release
# Step 3: Run tests with coverage collection
dotnet test --configuration Release `
--collect:"XPlat Code Coverage" `
--results-directory "TestResults" `
--logger "trx;LogFileName=test_results.trx" `
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
# Step 4: End analysis and upload results to SonarQube
dotnet sonarscanner end /d:sonar.token="$projectToken"
Write-Host "Analysis complete. View results at: http://localhost:9000/dashboard?id=my-dotnet-app"
Step 8: Configure Quality Gates
Quality gates define pass/fail criteria for code analysis results. The built-in “Sonar way” gate is a sensible starting point; create a custom gate for stricter or more lenient requirements.
$adminBase64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("admin:YourNewAdminPassword!"))
$headers = @{
Authorization = "Basic $adminBase64"
"Content-Type" = "application/x-www-form-urlencoded"
}
# Create a custom quality gate
$gateResponse = Invoke-RestMethod `
-Uri "http://localhost:9000/api/qualitygates/create" `
-Method POST -Headers $headers `
-Body "name=EnterpriseGate"
$gateId = $gateResponse.id
Write-Host "Created quality gate ID: $gateId"
# Add conditions to the gate:
# Condition 1: New critical bugs = 0
Invoke-RestMethod -Uri "http://localhost:9000/api/qualitygates/create_condition" `
-Method POST -Headers $headers `
-Body "gateId=$gateId&metric=new_blocker_violations&op=GT&error=0"
# Condition 2: New code coverage >= 80%
Invoke-RestMethod -Uri "http://localhost:9000/api/qualitygates/create_condition" `
-Method POST -Headers $headers `
-Body "gateId=$gateId&metric=new_coverage&op=LT&error=80"
# Condition 3: New security hotspots reviewed = 100%
Invoke-RestMethod -Uri "http://localhost:9000/api/qualitygates/create_condition" `
-Method POST -Headers $headers `
-Body "gateId=$gateId&metric=new_security_hotspots_reviewed&op=LT&error=100"
# Set this gate as the default (applies to all new projects)
Invoke-RestMethod -Uri "http://localhost:9000/api/qualitygates/set_as_default" `
-Method POST -Headers $headers `
-Body "id=$gateId"
Write-Host "Custom quality gate configured and set as default."
# Assign the gate to a specific project:
Invoke-RestMethod -Uri "http://localhost:9000/api/qualitygates/select" `
-Method POST -Headers $headers `
-Body "projectKey=my-dotnet-app&gateId=$gateId"
Step 9: Routine Maintenance
# View SonarQube service and process health
Get-Service -Name "SonarQube" | Select-Object Name, Status
Get-Process -Name "java" | Select-Object Id, CPU, WorkingSet64, ProcessName
# Check SonarQube version and database migration status
Invoke-RestMethod -Uri "http://localhost:9000/api/system/status" -UseBasicParsing
# Rotate logs — SonarQube logs rotate automatically but you can clean old logs:
Get-ChildItem "C:SonarQubecurrentlogs" -Filter "*.log.*" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
# Backup sonar.properties and the PostgreSQL database before upgrades:
Copy-Item "C:SonarQubecurrentconfsonar.properties" `
-Destination "C:SonarQubebackupssonar.properties.$(Get-Date -Format 'yyyyMMdd')"
& pg_dump -U sonarqube -h localhost sonarqube | Out-File `
-FilePath "C:SonarQubebackupssonarqube_db_$(Get-Date -Format 'yyyyMMdd').sql" -Encoding UTF8
Write-Host "Backup complete."
Installing SonarQube on Windows Server 2025 gives your development organization a persistent, always-available code quality analysis platform that integrates directly into CI/CD pipelines via SonarScanner for .NET, Maven, Gradle, or CLI scanners. By pairing PostgreSQL for reliable data storage, configuring custom quality gates with coverage and security thresholds, and integrating SonarQube analysis into your Azure DevOps or Jenkins pipelines, you create an automated quality enforcement layer that catches bugs and vulnerabilities before they reach production. As your team grows, consider upgrading to SonarQube Developer or Enterprise Edition for additional language support, branch analysis, pull request decoration, and SAML-based SSO integration with Azure Active Directory.