How to Set Up Gitea on Windows Server 2025

Gitea is a lightweight, self-hosted Git service that delivers a GitHub-like web interface without the resource overhead of GitLab or the licensing cost of GitHub Enterprise. Written in Go, Gitea ships as a single binary — an ideal fit for Windows Server 2025, where you can run it as a Windows Service, back it with SQLite for small teams or SQL Server/PostgreSQL for larger installations, and place it behind an Nginx reverse proxy with TLS. This guide walks you through every stage of a production-ready Gitea deployment: downloading the binary, creating a dedicated service account, writing the configuration file, registering Gitea as a Windows Service, completing the setup wizard, configuring SMTP, enabling the built-in SSH server, and exposing Gitea through Nginx.

Prerequisites

  • Windows Server 2025 (Standard or Datacenter)
  • PowerShell 7.4 or later with administrator rights
  • At least 1 GB RAM dedicated to Gitea (2 GB or more recommended for teams)
  • Git for Windows installed (required for Gitea’s internal Git operations) — see the previous tutorial
  • Nginx for Windows if you plan to use a reverse proxy (or IIS with ARR)
  • NSSM (Non-Sucking Service Manager) or sc.exe for Windows Service registration
  • SQL Server, MySQL, PostgreSQL, or SQLite (SQLite works out of the box for small teams)
  • A domain name or DNS entry pointing to the server if hosting externally

Step 1: Downloading the Gitea Binary

Gitea releases are published at https://dl.gitea.com/gitea/. Download the Windows AMD64 binary for the latest stable release. The binary is self-contained — it includes the web server, Git HTTP backend, and SSH server.

# Set the version to install
$giteaVersion = '1.22.3'
$giteaUrl     = "https://dl.gitea.com/gitea/$giteaVersion/gitea-$giteaVersion-windows-4.0-amd64.exe"
$installDir   = 'C:gitea'
$giteaBinary  = "$installDirgitea.exe"

# Create the installation directory
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
New-Item -ItemType Directory -Path "$installDircustomconf" -Force | Out-Null
New-Item -ItemType Directory -Path "$installDirdata"        -Force | Out-Null
New-Item -ItemType Directory -Path "$installDirlog"         -Force | Out-Null
New-Item -ItemType Directory -Path 'D:gitea-repositories'  -Force | Out-Null

# Download
Write-Host "Downloading Gitea $giteaVersion..."
Invoke-WebRequest -Uri $giteaUrl -OutFile $giteaBinary -UseBasicParsing

# Verify the download
if (-not (Test-Path $giteaBinary)) {
    throw "Gitea binary not found after download."
}

& $giteaBinary --version

Step 2: Creating a Dedicated Gitea Service Account

Running Gitea under a dedicated local account (rather than SYSTEM or an administrator account) follows the principle of least privilege. The account needs access only to the Gitea directories and, if using SQL Server, the database.

# Create a local service account for Gitea
$giteaUser     = 'svc_gitea'
$giteaPassword = ConvertTo-SecureString 'G1t3a$3rv1c3P@ss!' -AsPlainText -Force

New-LocalUser -Name $giteaUser -Password $giteaPassword `
    -Description 'Gitea Git service account' `
    -PasswordNeverExpires -UserMayNotChangePassword

# Grant the account 'Log on as a service' right
$ntrights = @'
[Unicode]
Unicode=yes
[System Access]
[Privilege Rights]
SeServiceLogonRight = *svc_gitea
'@

$tmpCfg = "$env:TEMPgitea_secedit.inf"
$tmpDb  = "$env:TEMPgitea_secedit.sdb"
$ntrights | Out-File -FilePath $tmpCfg -Encoding unicode

secedit /import /cfg $tmpCfg /db $tmpDb /quiet
secedit /configure /db $tmpDb /quiet
Remove-Item $tmpCfg, $tmpDb -Force -ErrorAction SilentlyContinue

# Grant ownership of Gitea directories
$giteaDirs = @('C:gitea', 'D:gitea-repositories')
foreach ($dir in $giteaDirs) {
    $acl  = Get-Acl -Path $dir
    $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
        $giteaUser, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
    $acl.SetAccessRule($rule)
    Set-Acl -Path $dir -AclObject $acl
    Write-Host "Permissions set for $giteaUser on $dir"
}

Step 3: Writing the app.ini Configuration File

Gitea’s configuration lives in C:giteacustomconfapp.ini. This INI file controls every aspect of Gitea’s behaviour — the work directory, HTTP port, root URL, database backend, SSH settings, and more. The following configuration uses SQLite for simplicity; see the database section for SQL Server settings.

; C:giteacustomconfapp.ini

APP_NAME = Our Internal Gitea
RUN_USER = svc_gitea
RUN_MODE = prod
WORK_PATH = C:gitea

[server]
PROTOCOL         = http
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3000
ROOT_URL         = https://git.example.com/
DOMAIN           = git.example.com
SSH_DOMAIN       = git.example.com
START_SSH_SERVER = true
SSH_PORT         = 2222
DISABLE_SSH      = false
LFS_START_SERVER = true
OFFLINE_MODE     = false

[database]
; SQLite — suitable for teams up to ~50 users
DB_TYPE  = sqlite3
PATH     = C:giteadatagitea.db

; --- SQL Server (uncomment and fill in for larger deployments) ---
; DB_TYPE  = mssql
; HOST     = sqlserver01:1433
; NAME     = GitteaDB
; USER     = gitea_user
; PASSWD   = YourStrongPassword

[repository]
ROOT = D:gitea-repositories

[log]
ROOT_PATH = C:gitealog
LEVEL     = Info

[security]
SECRET_KEY          = 
INTERNAL_TOKEN      = 
INSTALL_LOCK        = false
MIN_PASSWORD_LENGTH = 12
PASSWORD_COMPLEXITY = lower,upper,digit,spec

[mailer]
ENABLED        = true
FROM           = [email protected]
SMTP_ADDR      = smtp.example.com
SMTP_PORT      = 587
IS_TLS_ENABLED = true
USER           = [email protected]
PASSWD         = YourSmtpPassword

[service]
REGISTER_EMAIL_CONFIRM            = true
ENABLE_NOTIFY_MAIL                = true
DISABLE_REGISTRATION              = false
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = true
REQUIRE_SIGNIN_VIEW               = false
DEFAULT_KEEP_EMAIL_PRIVATE        = true
DEFAULT_ALLOW_CREATE_ORGANIZATION = true

[session]
PROVIDER        = file
PROVIDER_CONFIG = C:giteadatasessions

[picture]
AVATAR_UPLOAD_PATH     = C:giteadataavatars
REPOSITORY_AVATAR_PATH = C:giteadatarepo-avatars

Generate the security tokens before first run:

& 'C:giteagitea.exe' generate secret SECRET_KEY
& 'C:giteagitea.exe' generate secret INTERNAL_TOKEN

Paste the generated values into the SECRET_KEY and INTERNAL_TOKEN fields in app.ini.

Step 4: Registering Gitea as a Windows Service

NSSM (Non-Sucking Service Manager) is the most reliable way to wrap any executable as a Windows Service. Download NSSM from nssm.cc and place nssm.exe in C:toolsnssm.

# Alternative 1: Using NSSM (recommended)
$nssmPath    = 'C:toolsnssmwin64nssm.exe'
$serviceName = 'gitea'

& $nssmPath install $serviceName 'C:giteagitea.exe'
& $nssmPath set $serviceName AppParameters 'web --config C:giteacustomconfapp.ini'
& $nssmPath set $serviceName AppDirectory   'C:gitea'
& $nssmPath set $serviceName DisplayName    'Gitea - Git Service'
& $nssmPath set $serviceName Description    'Self-hosted Git service (Gitea)'
& $nssmPath set $serviceName Start          SERVICE_AUTO_START
& $nssmPath set $serviceName ObjectName     ".svc_gitea" 'G1t3a$3rv1c3P@ss!'
& $nssmPath set $serviceName AppStdout      'C:gitealognssm_stdout.log'
& $nssmPath set $serviceName AppStderr      'C:gitealognssm_stderr.log'
& $nssmPath set $serviceName AppRotateFiles 1
& $nssmPath set $serviceName AppRotateBytes 10485760   # 10 MB rotation

# Alternative 2: Using sc.exe (no third-party tool required)
# sc.exe create gitea binPath= "C:giteagitea.exe web --config C:giteacustomconfapp.ini" `
#         start= auto obj= ".svc_gitea" password= 'G1t3a$3rv1c3P@ss!' `
#         DisplayName= "Gitea - Git Service"

# Start the service
Start-Service -Name $serviceName
Get-Service -Name $serviceName | Select-Object Name, Status, StartType

Step 5: Running the Gitea Setup Wizard

On first run, Gitea presents an installation wizard at http://127.0.0.1:3000. Open a browser on the server and navigate there. The wizard lets you confirm database settings, set the administrator account credentials, and review server configuration. After completing the wizard, Gitea writes INSTALL_LOCK = true to app.ini to prevent the wizard from appearing again.

# Open the setup wizard in the default browser
Start-Process 'http://127.0.0.1:3000'

# Or verify Gitea is listening
Test-NetConnection -ComputerName 127.0.0.1 -Port 3000 | Select-Object TcpTestSucceeded

# After completing the wizard, verify INSTALL_LOCK
Select-String -Path 'C:giteacustomconfapp.ini' -Pattern 'INSTALL_LOCK'

During the wizard, create an administrator account. All subsequent user registrations can be controlled via the admin panel under Site Administration → User Management.

Step 6: Configuring the Built-in SSH Server

Gitea includes its own SSH server (separate from Windows OpenSSH), which listens on the port defined by SSH_PORT in app.ini. This is the recommended approach on Windows Server 2025 because it avoids conflicts with the built-in OpenSSH service.

# Ensure Windows Firewall allows Gitea's SSH port
New-NetFirewallRule -DisplayName 'Gitea SSH (2222)' `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 2222 `
    -Action Allow `
    -Profile Any

# Also allow the HTTP port (before Nginx is in place)
New-NetFirewallRule -DisplayName 'Gitea HTTP (3000)' `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 3000 `
    -Action Allow `
    -Profile Any

# Test SSH connectivity after adding an SSH key in Gitea web UI
ssh -T -p 2222 [email protected]

Step 7: Configuring Nginx as a Reverse Proxy

Exposing Gitea directly on port 3000 is not suitable for production. Place Nginx in front of Gitea to handle TLS termination, compression, and port 443. Download Nginx for Windows from nginx.org and place it in C:nginx.

# C:nginxconfnginx.conf (relevant server block)

server {
    listen 80;
    server_name git.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name git.example.com;

    ssl_certificate     C:/nginx/ssl/git.example.com.crt;
    ssl_certificate_key C:/nginx/ssl/git.example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    client_max_body_size 512M;

    location / {
        proxy_pass         http://127.0.0.1:3000;
        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_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}
# Register Nginx as a Windows service with NSSM
& 'C:toolsnssmwin64nssm.exe' install nginx 'C:nginxnginx.exe'
& 'C:toolsnssmwin64nssm.exe' set nginx AppDirectory 'C:nginx'
& 'C:toolsnssmwin64nssm.exe' set nginx Start SERVICE_AUTO_START

Start-Service nginx

# Open HTTPS in the firewall
New-NetFirewallRule -DisplayName 'Nginx HTTPS' -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow
New-NetFirewallRule -DisplayName 'Nginx HTTP'  -Direction Inbound -Protocol TCP -LocalPort 80  -Action Allow

Gitea on Windows Server 2025 provides a surprisingly capable self-hosted Git platform with minimal resource requirements. The single-binary architecture means upgrades are as simple as stopping the service, replacing gitea.exe, and restarting — a process that takes under a minute. With NSSM managing the Windows Service lifecycle, Nginx handling TLS and reverse proxying, and the built-in SSH server handling key-based Git operations, you have a fully production-ready internal Git service. For larger teams, migrate the database from SQLite to SQL Server or PostgreSQL, enable Gitea’s built-in Actions runner for CI/CD, and configure LDAP or Active Directory integration for single sign-on.