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.
“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.
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 3000on Linux ornetstat -ano | findstr :3000on Windows. - 502 Bad Gateway — nginx cannot reach the upstream server. Verify the
proxy_passaddress 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
locationblock 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.