DEV Community

Basic Load Balancing for a Web System on DigitalOcean

In this article we’ll build a simple and inexpensive load-balanced web setup on DigitalOcean.

The architecture is intentionally minimal:

  • 1 DigitalOcean Load Balancer
  • 2 small droplets
  • A lightweight web app running with Docker + PHP + Nginx

The goal is to show how quickly you can build a basic scalable web entry point without complex infrastructure.

Architecture diagram created with savnet.co:

architecture


Creating the first server

Go to your DigitalOcean account and create a small droplet.

Configuration used in this guide:

  • Image: Docker latest on Ubuntu 22.04
  • Region: choose the closest to your users
  • Authentication: SSH recommended (Password also works)
  • Hostname: WebServer-1

create-server-1

Once the droplet is ready, connect to the server.


Preparing the server

1. Create a user

adduser web_app
usermod -aG sudo web_app
sudo usermod -aG docker web_app
su - web_app
Enter fullscreen mode Exit fullscreen mode

This user will run the application and manage Docker.


2. Create the application folder

mkdir -p ~/app
cd ~/app
Enter fullscreen mode Exit fullscreen mode

3. Create the application files

index.php

A very small script that prints the server IP responding to the request.

<?php

echo 'Hi. From ip '.$_SERVER['SERVER_ADDR'];
Enter fullscreen mode Exit fullscreen mode

nginx.conf

Basic Nginx configuration to serve PHP via PHP-FPM.

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

This setup runs Nginx + PHP-FPM using Docker.

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    container_name: nginx-web
    network_mode: host
    volumes:
      - ./:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php-fpm

  php-fpm:
    image: php:8.2-fpm
    container_name: php-app
    network_mode: host
    volumes:
      - ./:/var/www/html
Enter fullscreen mode Exit fullscreen mode

4. Start the application

Run:

docker compose up
Enter fullscreen mode Exit fullscreen mode

If everything is correct you should see the containers starting.

app-start

Open the server IP in your browser.

You should see something like:

Hi. From ip 10.x.x.x
Enter fullscreen mode Exit fullscreen mode

app-running


Creating the Load Balancer

Now we create the DigitalOcean Load Balancer.

load-balancer

Configure:

  • HTTP forwarding
  • Attach WebServer-1

Once created, open the load balancer IP.

You’ll see something similar to this:

lb-response

Notice that the response shows an internal server IP, because traffic between the load balancer and droplets happens inside DigitalOcean’s network.


Creating the second server faster

Instead of repeating the setup manually, we can create a snapshot of the first droplet.

snapshot

Then create a new droplet from that snapshot in the same region.

snapshot-server


Adding the new node to the Load Balancer

Go back to the Load Balancer configuration and attach the second server.

attach-node

After about a minute, DigitalOcean will mark the droplet as healthy.

Now refresh the Load Balancer IP multiple times.

You should see responses coming from different internal IPs, meaning traffic is being distributed.

node1

node2


Security recommendations

This example focuses on the basic load balancing setup, but in a real environment you should also:

  • Add a DigitalOcean firewall
  • Restrict droplets so they are only reachable from the load balancer
  • Enable HTTPS
  • Configure health checks

These small changes significantly improve the security of the system.


Tools used

  • OBS – screen recording
  • Kdenlive – video editing
  • savnet.co – architecture diagrams used in this article

Top comments (0)