How to Deploy to a Brand New Server from Scratch

A step-by-step plan to take an empty Linux or Windows server to a live, TLS-protected app. Includes provisioning, code transfer, web server config, and verification.

Rao Aadil, India 11 min read

Deploying an application to a new server feels like a long list of manual steps until you break it into a sequence, and then it becomes repeatable. The steps below cover deploying to a brand new server from scratch on Linux or Windows, with commands for both platforms side by side: assessing the machine, installing dependencies, moving code, configuring a web server, securing TLS, and verifying the release.

Assess the brand new server and confirm it is empty

Start by connecting to the machine. On Linux, use SSH: ssh user@server_ip. On Windows, you can use RDP for a GUI session, but PowerShell remoting is easier to script: Enter-PSSession -ComputerName server_ip -Credential (Get-Credential).

Once connected, confirm you have administrative rights. On Linux, run sudo -v to check sudo access. On Windows, verify you are in the local Administrators group with whoami /groups | findstr /i "S-1-5-32-544".

Inspect the operating system, disk space, and package manager status.

Linux:

uname -a
cat /etc/os-release
df -h
apt --version   # or yum --version on RHEL/CentOS

Windows PowerShell:

Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, OSArchitecture
Get-PSDrive -PSProvider FileSystem

Check the firewall state.

Linux:

sudo ufw status           # if using UFW
# or
sudo firewall-cmd --state # if using firewalld

Windows:

Get-NetFirewallProfile | Select-Object Name, Enabled

Confirm that no web server, database, or application is already running. Look for common services.

Linux:

sudo systemctl list-units --type=service --state=running | grep -E 'nginx|apache2|mysql|postgres|docker'

Windows:

Get-Service | Where-Object {$_.Status -eq 'Running' -and $_.Name -match 'W3SVC|nginx|mysql|postgres|docker'}

If nothing appears, the server is clean and ready for the first deploy.

Doing this with brynko devOps Agent

“There is nothing to deploy onto yet, so the first release means an afternoon of setup before any code moves.”

It installs what the project needs, sets up the web server, moves the app across and issues the certificate, showing you the whole plan before step one.

It reads the real state of the server before it says anything, shows you the exact command, and waits for your approval. Windows and Linux, over the SSH access you already have.

Download for WindowsSee what else it does

Choose your stack and plan the deployment sequence

Decide whether to install the application directly on the host or run it inside Docker. Direct installation is simpler and has fewer moving parts, but dependencies can conflict. Docker isolates the runtime and makes the environment reproducible; on Linux it runs natively, while on Windows you can use Docker Desktop for development or Docker Engine with Windows containers on server editions.

Map the application requirements before installing anything:

  • Runtime and version (Node.js, Python, .NET, Java, etc.)
  • Database server and whether it is installed on the same machine or a separate host
  • Web server or reverse proxy (nginx, IIS, or the app's built-in server)
  • Ports that must be open (typically 80, 443, and the app's internal port)
  • Environment variables and secrets
  • TLS certificate domain

Draft the order of operations: provision dependencies, transfer code, configure the web server, issue TLS, then run and verify the deployment. This order prevents a half-configured server from being exposed.

Install dependencies and web server

If you are using an AI-assisted deployment capability, it can write these installation commands as a script and run them with your approval, streaming the output and logging each step to an audit log. The same commands are shown here for manual execution.

Linux (Debian/Ubuntu) — install nginx, PostgreSQL, and Node.js as an example. Replace with your runtime.

sudo apt update
sudo apt install -y nginx postgresql nodejs npm

Linux (RHEL/CentOS)

sudo yum install -y nginx postgresql-server nodejs npm

Start and enable the services:

sudo systemctl enable --now nginx postgresql

Windows with IIS — enable the web server role and install a runtime with winget or Chocolatey.

Install-WindowsFeature -Name Web-Server -IncludeManagementTools
winget install OpenJS.NodeJS.LTS   # example for Node.js
# or
choco install nodejs-lts -y

Windows with nginx — download the zip from nginx.org and extract it to C:\nginx, or use Chocolatey:

choco install nginx -y

Start nginx by running C:\nginx\nginx.exe. To install it as a Windows service, use a tool like NSSM.

Install the database on Windows using its official installer, or with Chocolatey:

choco install postgresql -y   # or choco install mysql -y

Docker alternative — on Linux, install Docker Engine and the compose plugin using the distribution's package manager or the official repository instructions. On Windows, install Docker Desktop with winget install Docker.DockerDesktop for development, or use Docker Engine with Windows containers on Windows Server.

After installing, open the necessary ports. Linux with UFW:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Windows:

New-NetFirewallRule -DisplayName "Allow HTTP" -Direction Inbound -LocalPort 80 -Protocol TCP -Action Allow
New-NetFirewallRule -DisplayName "Allow HTTPS" -Direction Inbound -LocalPort 443 -Protocol TCP -Action Allow

Move application code to the server

Use git clone or file copy. Git is preferred because it makes future updates easier.

Linux:

sudo mkdir -p /var/www/app
sudo chown -R $USER:$USER /var/www/app
git clone https://your-repo.git /var/www/app

If you prefer to copy a local directory over SSH, use rsync or scp:

rsync -avz --delete ./app/ user@server_ip:/var/www/app/
# or
scp -r ./app user@server_ip:/var/www/app/

Windows:

New-Item -ItemType Directory -Path C:\inetpub\wwwroot\app
git clone https://your-repo.git C:\inetpub\wwwroot\app

To copy files from a local Windows machine, use Copy-Item to a UNC path:

Copy-Item -Path .\app\* -Destination \\server_ip\C$\inetpub\wwwroot\app -Recurse

Set correct ownership so the web server can read and execute the application files. On Linux, if nginx runs as www-data:

sudo chown -R www-data:www-data /var/www/app
sudo chmod -R 755 /var/www/app

Adjust the user if your application runs under a different account. On Windows, grant the IIS application pool identity modify permissions:

$appPool = "IIS AppPool\YourAppPool"
icacls C:\inetpub\wwwroot\app /grant "$appPool:(OI)(CI)M"

If using nginx on Windows, grant the user that runs the nginx process access to the app directory.

Configure web server and reverse proxy

Nginx on Linux — create a server block in /etc/nginx/sites-available/app:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Enable the site and test the configuration:

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Nginx on Windows — edit C:\nginx\conf\nginx.conf and add the same server block inside the http context. Test and reload from the nginx directory:

C:\nginx\nginx.exe -t
C:\nginx\nginx.exe -s reload

IIS — create a site and application pool with PowerShell:

Import-Module WebAdministration
New-Website -Name "MyApp" -PhysicalPath "C:\inetpub\wwwroot\app" -BindingInformation "*:80:example.com"

If your app listens on a separate port (e.g., 3000), configure IIS as a reverse proxy using the Application Request Routing (ARR) module and URL Rewrite rules. At minimum, set the application pool identity and .NET CLR version to match your app.

Before applying any configuration, review it as a diff. If you are editing manually, keep a backup copy of the original file. After reloading, confirm the service is running with systemctl status nginx on Linux or Get-Service W3SVC on Windows.

Issue TLS certificate and enable HTTPS

Linux with nginx and Let's Encrypt — install certbot and request a certificate:

sudo apt install -y certbot python3-certbot-nginx   # Debian/Ubuntu
# or
sudo yum install -y certbot python3-certbot-nginx   # RHEL/CentOS
sudo certbot --nginx -d example.com

Certbot edits the nginx configuration automatically and sets up redirects. Automatic renewal is enabled by a systemd timer; verify with systemctl list-timers | grep certbot.

Windows with IIS — install win-acme (also called WACS). Download the zip from the win-acme GitHub releases, extract it to C:\win-acme, and run wacs.exe. Select N for a new certificate, pick the IIS site, and accept the defaults. win-acme updates the IIS bindings and stores the certificate.

Windows with nginx — win-acme can also obtain and store certificate files for manual use. Point the nginx configuration to the generated .pem files and add an HTTPS server block. Note that the AI-assisted deployment tool's bare-server setup installs certbot on Linux; Windows users should verify the TLS automation path for their chosen ACME client.

To force HTTP to HTTPS on nginx, add a redirect server block:

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

On IIS, use URL Rewrite with a rule that redirects HTTP to HTTPS, or set the site's bindings to require SSL.

Run the first deployment and verify the site

Run the deploy script you have written, or trigger the pipeline later. Stream the output and watch for errors.

Linux Bash:

./deploy.sh

Windows PowerShell:

.\deploy.ps1

Common failure causes:

  • connection refused — the application is not listening on the expected port, or the firewall is blocking it. Check with ss -ltnp | grep 3000 on Linux or netstat -ano | findstr :3000 on Windows.
  • 502 Bad Gateway — nginx cannot reach the upstream server. Verify the proxy_pass address and that the app is running and bound to the same port.
  • permission denied — the web server user cannot read the app files. Re-check ownership as shown earlier.
  • 404 Not Found — the static file path or application route is wrong. Confirm the location block and document root.

Verify externally with curl or Invoke-WebRequest.

Linux:

curl -I https://example.com
curl -sS https://example.com | head -n 10
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

Windows PowerShell:

Invoke-WebRequest -Uri https://example.com -UseBasicParsing | Select-Object StatusCode, Content
curl.exe -I https://example.com

Check that the HTTP status is 200, the content is correct, and the certificate is valid. If you see certificate verify failed, the certificate is not trusted or the domain does not match.

Save the deployment as code to prevent future manual steps

Store the deploy script in your script library so you can run it again without retyping commands. A Bash or PowerShell script that installs dependencies, pulls code, restarts services, and verifies the site turns the whole process into a single command.

Add a Jenkinsfile or GitHub Actions workflow to automate future deployments. For example, a simple GitHub Actions workflow that connects over SSH and pulls the latest code:

name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy over SSH
        run: |
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > key.pem
          chmod 600 key.pem
          ssh -i key.pem -o StrictHostKeyChecking=no user@server_ip "cd /var/www/app && git pull && sudo systemctl restart myapp"

A Jenkinsfile can run the same SSH command in a pipeline stage.

For infrastructure as code, write Terraform files that describe the server, then run terraform plan to review changes and terraform apply after approval. This makes it possible to provision an identical brand new server later without clicking through a cloud console.

FAQ: first deploy on a new server

How do I know if my server is truly empty, and what basic checks should I run first? Connect over SSH or PowerShell remoting, verify you have admin rights, check OS and disk with uname -a / Get-CimInstance, inspect firewall status, and list running services. If no web server, database, or application service appears, the server is empty.

Which web server should I use on a brand new Linux server: nginx or Apache? Nginx is usually the easier choice for a reverse proxy and static file serving because of its simple configuration and low memory footprint. Apache is equally capable and may be preferable if you need specific modules or .htaccess support.

Do I need Docker on a new server, or can I deploy directly with a script? Docker is not required. Direct deployment with a Bash or PowerShell script is simpler for small apps and gives you full control. Use Docker when the app has complex dependencies or you want identical environments across machines.

How do I get a free SSL certificate on Windows without certbot? Use win-acme (WACS), a free ACME client for Windows. It can obtain a Let's Encrypt certificate for an IIS site automatically, and it can also export certificate files for nginx on Windows.

Why does my site show 502 Bad Gateway after I set up nginx and my app? The most common cause is that nginx cannot reach the upstream application server. Verify the app is running and listening on the port specified in proxy_pass, and check the firewall rules between nginx and the app.

Can I use one command to install everything and deploy, or do I need to run steps manually? You can write a single Bash or PowerShell script that runs all the steps in order and execute it as one command. It is still worth running the steps manually at least once to understand each part and make debugging easier.

What is the difference between deploying to staging and production on a brand new server? Staging uses the same configuration but a separate hostname, database, and usually a test certificate or self-signed TLS. Production adds stricter access controls, real user data, valid TLS, backups, monitoring, and approval gates before changes go live.

Related reading

Get your new server live without manual guesswork

Use an AI-assisted deployment to write the scripts and configs, run each step with approval, and verify the release end to end.

An AI teammate that builds, runs, fixes, deploys and operates your software, on your machine and on your servers. It reads the real state of your systems, shows you every command before it runs, and never touches production without your approval.

Works with Docker Docker Compose nginx IIS Tomcat MySQL / MariaDB PostgreSQL Redis Linux servers Windows servers AWS Jenkins Let's Encrypt Node.js Python Java .NET Go PHP
  • Every change asks first, and shows you the exact command or diff before it runs.
  • File edits are snapshotted and reversible. Deleted folders are archived, not removed.
  • Private key material never reaches the model, under any setting.
brynko devOps Agent
Deployments

this project has no deploy yet, get it onto staging

read the project checked the server drafted the deploy script

It is a Node app with a Dockerfile but no deploy script. I have drafted one that builds the image, replaces the container and reloads nginx; approve it on the card and it is saved to your scripts under Staging. Then shall I run it?

go ahead

streamed the run container replaced site responded

Finished in 1 minute 52. The new container is running, nginx reloaded cleanly, and staging is returning 200. Next time it is one click in Scripts, or just ask.

Deployed · staging healthy

Free download for Windows. 300 free credits on signup, no card, no subscription.

Questions

Common questions

How do I know if my server is truly empty, and what basic checks should I run first?
Connect over SSH or PowerShell remoting, verify you have admin rights, check OS and disk with uname -a / Get-CimInstance, inspect firewall status, and list running services. If no web server, database, or application service appears, the server is empty.
Which web server should I use on a brand new Linux server: nginx or Apache?
Nginx is usually the easier choice for a reverse proxy and static file serving because of its simple configuration and low memory footprint. Apache is equally capable and may be preferable if you need specific modules or .htaccess support.
Do I need Docker on a new server, or can I deploy directly with a script?
Docker is not required. Direct deployment with a Bash or PowerShell script is simpler for small apps and gives you full control. Use Docker when the app has complex dependencies or you want identical environments across machines.
How do I get a free SSL certificate on Windows without certbot?
Use win-acme (WACS), a free ACME client for Windows. It can obtain a Let's Encrypt certificate for an IIS site automatically, and it can also export certificate files for nginx on Windows.
Why does my site show 502 Bad Gateway after I set up nginx and my app?
The most common cause is that nginx cannot reach the upstream application server. Verify the app is running and listening on the port specified in proxy_pass, and check the firewall rules between nginx and the app.
Can I use one command to install everything and deploy, or do I need to run steps manually?
You can write a single Bash or PowerShell script that runs all the steps in order and execute it as one command. It is still worth running the steps manually at least once to understand each part and make debugging easier.
What is the difference between deploying to staging and production on a brand new server?
Staging uses the same configuration but a separate hostname, database, and usually a test certificate or self-signed TLS. Production adds stricter access controls, real user data, valid TLS, backups, monitoring, and approval gates before changes go live.

Keep reading