What Is Gitea and Why Run It on Windows Server 2022

Gitea is a lightweight, self-hosted Git service written in Go. It provides a GitHub-style web interface for browsing repositories, reviewing pull requests, managing organizations, and running basic CI/CD through Gitea Actions. Because it ships as a single binary with no runtime dependencies, it is easy to deploy on Windows Server 2022 without installing Node.js, Ruby, or Java. Teams that require an on-premises Git server for compliance, air-gapped environments, or cost reasons commonly choose Gitea for its low resource footprint and simple administration.

Downloading Gitea for Windows

Gitea provides pre-built Windows binaries on its releases page. Download the latest 64-bit executable and place it in a dedicated service directory:

# Create the Gitea directory structure
New-Item -ItemType Directory -Path "C:Giteabin"  -Force | Out-Null
New-Item -ItemType Directory -Path "C:Giteadata" -Force | Out-Null
New-Item -ItemType Directory -Path "C:Gitealog"  -Force | Out-Null
New-Item -ItemType Directory -Path "C:Gitearepos" -Force | Out-Null

# Download the binary (check https://dl.gitea.com/gitea/ for the latest version)
$giteaVersion = "1.22.1"
$giteaUrl = "https://dl.gitea.com/gitea/$giteaVersion/gitea-$giteaVersion-windows-4.0-amd64.exe"
Invoke-WebRequest -Uri $giteaUrl -OutFile "C:Giteabingitea.exe"

Verify the download by checking the SHA256 checksum published on the Gitea releases page:

Get-FileHash -Algorithm SHA256 "C:Giteabingitea.exe" | Select-Object Hash

Initial Configuration: app.ini

Gitea reads its configuration from a file named app.ini, located at C:Giteadatacustomconfapp.ini by default. Create this file before the first run to pre-configure the most important settings:

New-Item -ItemType Directory -Path "C:Giteadatacustomconf" -Force | Out-Null

$appIni = @"
APP_NAME = My Company Gitea
RUN_MODE = prod
RUN_USER = git

[server]
DOMAIN           = gitea.example.com
HTTP_PORT        = 3000
ROOT_URL         = http://gitea.example.com/
LOCAL_ROOT_URL   = http://localhost:3000/
SSH_DOMAIN       = gitea.example.com
SSH_PORT         = 22
SSH_LISTEN_PORT  = 2222
START_SSH_SERVER = true
OFFLINE_MODE     = false

[database]
DB_TYPE  = sqlite3
PATH     = C:/Gitea/data/gitea.db

[repository]
ROOT = C:/Gitea/repos

[log]
ROOT_PATH = C:/Gitea/log
MODE      = file
LEVEL     = Info

[security]
INSTALL_LOCK          = false
SECRET_KEY            = CHANGEME_replace_with_random_64char_string
INTERNAL_TOKEN        = CHANGEME_replace_with_random_token
PASSWORD_HASH_ALGO    = argon2

[service]
DISABLE_REGISTRATION              = false
REQUIRE_SIGNIN_VIEW               = false
ENABLE_NOTIFY_MAIL                = false
ALLOW_ONLY_INTERNAL_REGISTRATION  = true

[mailer]
ENABLED = false
"@

Set-Content -Path "C:Giteadatacustomconfapp.ini" -Value $appIni -Encoding UTF8

Replace the SECRET_KEY and INTERNAL_TOKEN values with random strings. You can generate them with:

-join ((1..64) | ForEach-Object { '{0:X}' -f (Get-Random -Maximum 16) })

Running Gitea as a Windows Service with NSSM

NSSM (Non-Sucking Service Manager) wraps any executable as a Windows service, handling restarts, logging, and startup order. Install NSSM first:

winget install --id NSSM.NSSM -e --source winget

Or download it manually from https://nssm.cc/download and place nssm.exe in a folder on the PATH. Then register Gitea as a service:

nssm install gitea "C:Giteabingitea.exe"
nssm set gitea AppParameters "web --config C:Giteadatacustomconfapp.ini"
nssm set gitea AppDirectory "C:Giteabin"
nssm set gitea DisplayName "Gitea Git Service"
nssm set gitea Description "Self-hosted Git service"
nssm set gitea Start SERVICE_AUTO_START
nssm set gitea AppStdout "C:Gitealogservice_stdout.log"
nssm set gitea AppStderr "C:Gitealogservice_stderr.log"
nssm set gitea AppRotateFiles 1
nssm set gitea AppRotateOnline 1
nssm set gitea AppRotateSeconds 86400

# Start the service
nssm start gitea

Verify the service is running:

Get-Service -Name gitea
# Status: Running

If you prefer not to use NSSM, Gitea also supports native Windows service registration:

cd C:Giteabin
.gitea.exe service install --config C:Giteadatacustomconfapp.ini
.gitea.exe service start

Database Setup: SQLite, MySQL, and PostgreSQL

The app.ini above uses SQLite, which is fine for teams up to around 20-50 users. For larger installations or better concurrency, use MySQL or PostgreSQL.

For MySQL, first create the database and user:

-- Run in MySQL client
CREATE DATABASE gitea CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'gitea'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'localhost';
FLUSH PRIVILEGES;

Then update the [database] section in app.ini:

[database]
DB_TYPE = mysql
HOST    = 127.0.0.1:3306
NAME    = gitea
USER    = gitea
PASSWD  = StrongPassword123!
CHARSET = utf8mb4

For PostgreSQL:

-- Run as postgres superuser
CREATE ROLE gitea WITH LOGIN PASSWORD 'StrongPassword123!';
CREATE DATABASE gitea OWNER gitea;
GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea;
[database]
DB_TYPE = postgres
HOST    = 127.0.0.1:5432
NAME    = gitea
USER    = gitea
PASSWD  = StrongPassword123!
SCHEMA  =
SSL_MODE = disable

Running the Setup Wizard

With the service running and the firewall port open, navigate to http://SERVER_IP:3000 in a browser. The Gitea installation wizard appears on the first visit. If you pre-configured app.ini, most fields are already populated. The wizard will:

  • Confirm the database connection and create the schema
  • Set the administrator account credentials
  • Finalize the INSTALL_LOCK = true flag in app.ini to prevent re-running the wizard

Open the firewall port before trying to access the interface:

New-NetFirewallRule -DisplayName "Gitea HTTP" -Direction Inbound `
    -Protocol TCP -LocalPort 3000 -Action Allow
New-NetFirewallRule -DisplayName "Gitea SSH" -Direction Inbound `
    -Protocol TCP -LocalPort 2222 -Action Allow

Configuring HTTPS with a Certificate

To enable HTTPS directly in Gitea (without a reverse proxy), place your certificate and private key files on the server and update app.ini:

[server]
PROTOCOL        = https
ROOT_URL        = https://gitea.example.com/
HTTP_PORT       = 443
CERT_FILE       = C:/Gitea/ssl/cert.pem
KEY_FILE        = C:/Gitea/ssl/key.pem

Restart the service after modifying app.ini:

nssm restart gitea

IIS as a Reverse Proxy for Gitea

Running IIS as a reverse proxy in front of Gitea lets you host Gitea on port 3000 internally while serving it over port 443 with an IIS-managed certificate. Install the URL Rewrite and Application Request Routing (ARR) modules, then configure a site:

Import-Module WebAdministration

# Enable proxy in ARR
Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/proxy" -Name "enabled" -Value $true

# Create the reverse proxy site
New-WebSite -Name "Gitea" -Port 443 -PhysicalPath "C:inetpubgitea" `
            -Ssl -SslFlags 0

# web.config with URL Rewrite rule for the Gitea site
$webConfig = @"


  
    
      
        
          
          
        
      
    
  

"@
New-Item -Path "C:inetpubgitea" -ItemType Directory -Force | Out-Null
Set-Content -Path "C:inetpubgiteaweb.config" -Value $webConfig

Update ROOT_URL in app.ini to the HTTPS address, and set HTTP_PORT = 3000 with PROTOCOL = http (Gitea itself stays on HTTP; IIS handles TLS).

Webhooks and CI Integration with Gitea Actions

Gitea supports webhooks at the repository, organization, and system level. To configure a webhook that notifies a build server on every push, go to the repository settings in the web UI, or configure it via the Gitea API:

# Gitea REST API — create a push webhook on a repo
$headers = @{
    "Authorization" = "token YOUR_GITEA_ACCESS_TOKEN"
    "Content-Type"  = "application/json"
}
$body = @{
    type        = "gitea"
    config      = @{ url = "https://jenkins.example.com/gitea-webhook/post"; content_type = "json" }
    events      = @("push", "pull_request")
    active      = $true
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://gitea.example.com/api/v1/repos/myorg/myrepo/hooks" `
                  -Method Post -Headers $headers -Body $body

Gitea Actions (available in Gitea 1.19+) uses workflow YAML files in .gitea/workflows/, compatible with GitHub Actions syntax. A basic CI workflow:

# .gitea/workflows/build.yml
name: Build and Test
on: [push, pull_request]

jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'
      - name: Restore
        run: dotnet restore
      - name: Build
        run: dotnet build --no-restore
      - name: Test
        run: dotnet test --no-build

To use Gitea Actions, you must register at least one Gitea Actions Runner. Download the runner binary from the Gitea releases page and register it:

act_runner.exe register --instance http://gitea.example.com `
    --token RUNNER_TOKEN_FROM_GITEA_SETTINGS `
    --name "WinServer2022-Runner" --no-interactive
act_runner.exe daemon

Backing Up Gitea

Gitea includes a built-in backup command that creates a zip archive of the database, configuration, repositories, and attachments:

# Run as the user that owns Gitea data
C:Giteabingitea.exe dump --config C:Giteadatacustomconfapp.ini `
    --file "C:Backupsgitea-backup-$(Get-Date -Format 'yyyyMMdd').zip"

Schedule this as a Windows Scheduled Task to run nightly:

$action = New-ScheduledTaskAction -Execute "C:Giteabingitea.exe" `
    -Argument "dump --config C:Giteadatacustomconfapp.ini --file C:Backupsgitea-backup.zip"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00AM"
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable
Register-ScheduledTask -TaskName "Gitea Backup" -Action $action `
    -Trigger $trigger -Settings $settings -RunLevel Highest

Store backups on a separate volume or UNC share, and periodically test restoration by unpacking the archive and running Gitea against it in a staging environment to confirm the backup is valid.