Можно ли на один VPS поставить несколько сервисов? Можно, все так делают — у меня на личном VPS крутятся таск-трекер, бот в Telegram, пара пет-проектов и Grafana, чтобы за всем этим следить. Однако немногие знают, как это сделать правильно, поэтому рассказываю под катом. 

VPS — это машина с root-доступом, поэтому ничего не мешает запускать на ней хоть десять проектов одновременно, были бы ресурсы. Весь веб-хостинг девяностых, кстати, начался ровно с этого — в 1996 году Apache научился отдавать разные сайты по разным доменным именам с одного сервера. В связи с этим сразу перехожу к вопросу «Как?».

HTTP-запрос содержит доменное имя внутри, в заголовке Host. Когда пользователь открывает tracker.example.com и bot.example.com, оба запроса прилетают на один IP и один порт 443, но с разными именами. Значит, на входе нужен диспетчер — это называется обратный прокси. Сразу отмечу, не копируйте слепо мои команды, они с моими примерами, адаптируйте или адаптируйтесь. 

Способ первый. Диспетчер на входе

В роли диспетчера обычно выступает nginx, Caddy или Traefik. Сервисы слушают свои внутренние порты, а наружу смотрит только прокси. Он же занимается TLS-сертификатами, редиректом с HTTP на HTTPS и отдаёт статику. 

Если хотите минимум возни, обратите внимание именно на Caddy. Он легкий и у него короткий конфиг: 

tracker.example.com {

reverse_proxy localhost:3000

}

bot.example.com {

reverse_proxy localhost:8080

}

Это реально вся конфигурация — Caddy сам настраивает HTTPS, получает сертификаты, например Let's Encrypt или ZeroSSL, и продлевает их. Для этого DNS-записи доменов должны указывать на сервер, а необходимые порты должны быть доступны извне. 

С nginx конфигурация длиннее, зато гибче и привычнее многим. Для каждого домена тут создается отдельный блок server, в котором указываются порт, доменное имя и адрес приложения: 

server {

    listen 80;

    server_name tracker.example.com;

    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;

    }

}

server {

    listen 80;

    server_name api.example.com;

    location / {

        proxy_pass http://127.0.0.1:3001;

        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;

    }

}

Здесь listen 80 указывает порт, на котором nginx принимает обычные HTTP-запросы, а server_name определяет, какому домену соответствует каждый блок. Директива proxy_pass передаёт запросы нужному приложению, причём оба сервиса слушают только локальный интерфейс 127.0.0.1. 

Два таких блока в конфиге, и вы получите два независимых сервиса на одной машине, каждый со своим доменом. Конфиг кладём в /etc/nginx/sites-available/ или conf.d/, дальше проверка синтаксиса и аккуратный перезапуск:

sudo nginx -t && sudo systemctl reload nginx

Если nginx обнаружит ошибку, исправьте указанную проблему и повторите проверку. Для подключения HTTPS можно использовать Certbot с плагином nginx — он получит TLS-сертификат, добавит необходимые настройки для порта 443 и сможет автоматически продлевать сертификат:

sudo certbot --nginx -d tracker.example.com -d api.example.com

Для этого DNS-записи обоих доменов должны указывать на сервер, входящие подключения к порту 80 должны быть разрешены, а Certbot и его плагин nginx — установлены. После проверьте автоматическое продление сертификатов:

sudo certbot renew --dry-run

Затем посмотрите, кто вообще что слушает на машине, и убедитесь, что наружу торчат только 80, 443 и SSH, а сервисы сидят на localhost:

sudo ss -tulpn

В выводе у tracker и api (это мои примеры) в колонке Local Address должно быть 127.0.0.1:3000 и 127.0.0.1:3001, а не 0.0.0.0, иначе их порты открыты всему интернету в обход прокси и файрвола. 

Если приложение умеет биндиться на адрес, пропишите в его конфиге 127.0.0.1. И, пожалуйста, разносите сервисы по поддоменам, а не по путям вроде example.com/tracker. С поддоменами меньше головной боли с куками, редиректами и путями статики внутри приложений.

Способ второй. Каждому сервису по контейнеру

Когда у сервисов разные зависимости, лучше упаковать каждый в свой docker-compose стек, а поверх поставить один общий прокси, который слушает внешние порты и раскидывает запросы по контейнерам. Для этого нужно создать общую внутреннюю сеть (это делается один раз):

docker network create front

Дальше один compose-файл с nginx или Caddy как входной точкой, и рядом по одному compose на каждый сервис, все они подключены к этой сети: 

services:

  proxy:

    image: caddy:2

    ports:

      - "80:80"

      - "443:443"

    volumes:

      - ./Caddyfile:/etc/caddy/Caddyfile:ro

      - caddy_data:/data

      - caddy_config:/config

    restart: unless-stopped

    networks:

      - front

  tracker:

    image: my-tracker:latest

    mem_limit: 512m

    restart: unless-stopped

    networks:

      - front

networks:

  front:

    external: true

volumes:

  caddy_data:

  caddy_config:

Обратите внимание, что наружу опубликованы только порты прокси. Таск-трекер доступен другим контейнерам в сети front на своем внутреннем порту 3000, а mem_limit ограничивает потребление памяти, чтобы утечка в приложении не съела всю машину.

Конфиг Caddy писал выше. Для контейнеров его нужно немного изменить — вместо localhost использовать имя сервиса внутри Docker-сети. В файл Caddyfile, который лежит рядом с compose.yaml, добавляем: 

tracker.example.com {

    reverse_proxy tracker:3000

}

Запуск и проверка, что всё поднялось так:

docker compose up -d

docker compose ps

Сумму жёстких лимитов держите ниже физической памяти машины, иначе вы просто отложили проблему. И не забудьте оставить запас ядру, SSH, Docker-демону и прокси. Сколько каждый контейнер реально ест памяти и CPU можно посмотреть через команду:

docker stats --no-stream

Плюс подхода в изоляции, зависимости одного сервиса не ломают другой, обновляете каждый отдельно. Минус — у каждого контейнера свой оверхед по памяти, ну и добавляется управление контейнерами, образами, сетями и томами.

Способ третий. Обычные процессы под разными пользователями

Самый старый вариант — это запустить каждый сервис обычным процессом, но под своим пользователем и под надзором systemd, который перезапустит упавшее и запишет логи. Создаём системного пользователя без шелла и домашней папки:

sudo useradd -r -U -s /usr/sbin/nologin tracker

sudo install -d -o tracker -g tracker /opt/tracker

После размещения приложения в /opt/tracker проверьте права на исполняемый файл run.sh, каталоги с данными и используемые конфигурационные файлы. Сервисный пользователь должен иметь доступ только к необходимым ресурсам:

И пишем юнит /etc/systemd/system/tracker.service:

[Unit]

Description=My tracker

After=network.target

[Service]

User=tracker

WorkingDirectory=/opt/tracker

ExecStart=/opt/tracker/run.sh

Restart=always

MemoryMax=512M

OOMScoreAdjust=500

[Install]

WantedBy=multi-user.target

MemoryMax — это жёсткий потолок памяти для сервиса, аналог mem_limit из контейнерного мира, а OOMScoreAdjust=500 повышает вероятность выбора процесса системным OOM killer. Также помните про MemoryHigh. Включается всё командами:

sudo systemctl daemon-reload

sudo systemctl enable --now tracker

sudo systemctl status tracker

Вывод сервиса попадает в системный журнал с фильтрацией. Например, команда ниже показывает сообщения tracker: 

journalctl -u tracker -f

К слову, разные пользователи заодно дают базовую изоляцию по файлам, один сервис физически не может прочитать конфиги другого без лишних прав. Для двух-трёх своих сервисов на одном VPS это, по мне, недооценённый вариант.

Сколько сервисов влезет и как проверить 

Четкий ответ не дать, зависит от вашей VPS. По моим оценкам, лёгкий Go- или Node-сервис в простое ест 50–150 МБ, Python-приложение обычно 150–400 МБ, а PostgreSQL для личных проектов нормально живёт в 300–500 МБ, если не забыть подрезать shared_buffers. Потребление памяти Redis зависит от объёма хранимых данных, структуры ключей и служебных расходов. Ну и Grafana с Prometheus могут попросить ещё полгига.

Итого на VPS с 2 гигабайтами могут жить четыре-пять лёгких сервисов без базы или два-три с базой. Личные проекты простаивают большую часть времени, и CPU отлично делится между ними по остаточному принципу. Конфигурацию надо проверять — реальное потребление каждого сервиса смотрите командой:

ps -eo pid,comm,rss --sort=-rss | head -15

free -h

Для первоначальной оценки смотрите на ps и free -h, а в более точном анализе учитывайте rss и потребление групп процессов через cgroups. Грубо говоря, прикиньте rss сервиса под нагрузкой, плюс запас в 30% на пики, плюс память базе, плюс саму систему с её кэшами. Если сумма влезает в объём машины с запасом, заселяйте, если впритык — либо апгрейд тарифа, либо расселение.

Также важно соседство по времени. Ночной бэкап одного сервиса упрётся в диск и задушит сайт другого. Лечится расписаниями и понижением приоритета фоновых задач, тот же pg_dump в кроне запускается с низким приоритетом по диску и процессору:

#!/usr/bin/env bash

set -euo pipefail

BACKUP_DIR=/backups

DATE=$(date +%F)

ionice -c 3 nice -n 19 \

  pg_dump -U app -d appdb \

  -Fc -f "$BACKUP_DIR/appdb_$DATE.dump"

Предварительно создайте каталог /backups, настройте права доступа и аутентификацию пользователя PostgreSQL, а скрипт сохраните в /usr/local/bin/backup-appdb.sh с разрешением на выполнение. После этого его можно запускать через cron:

0 3 * * * /usr/local/bin/backup-appdb.sh

Для регулярной эксплуатации нужны контроль успешного завершения, ротация архивов и отправка резервной копии в другое хранилище. 

Про безопасность

Общая машина — это общий периметр. Контейнеры и разные пользователи снижают риски, но ядро у всех одно, поэтому взлом одного дырявого сервиса может открыть дорогу к соседям. Минимально сделайте так, чтобы файрвол пропускал только веб и SSH, а всё остальное было закрыто:

sudo ufw allow 80/tcp

sudo ufw allow 443/tcp

sudo ufw allow 22/tcp

sudo ufw default deny incoming

sudo ufw enable

Если вы подключены к VPS по SSH, убедитесь, что разрешен именно тот порт, на котором работает SSH-сервер. Ошибка в правилах может привести к потере удаленного доступа. 

Также помните, что при публикации контейнерных портов Docker сам создает правила сетевой фильтрации и перенаправления трафика — правила UFW могут не блокировать доступ к опубликованным портам. В связи с этим, контейнеры с БД и внутренними API вообще не публикуйте наружу, а ограничения доступа сверьте с сетевой конфигурацией.

Из ИБ-правил побольше я придерживаюсь двух: 

  • Тестовые и экспериментальные поделки не должны жить на той же машине, что и сервис с реальными пользователями и их данными. Для экспериментов нужна отдельная дешёвая виртуалка, которую не жалко снести целиком. 

  • Чем старше и неизвестнее софт, тем сильнее изоляция. Пятилетний WordPress с самописными плагинами и свежий Go-сервис — это разные уровни доверия, и сажать их рядом я не советую. 

Бэкапы и мониторинг лучше отправить к соседям

Снапшот VPS помогает быстро откатиться перед обновлением, но резервную копию всё равно отправляйте в другое хранилище (снапшот — это не бэкап). 

Для PostgreSQL дополнительно используйте pg_dump или pg_dumpall, помня, что второй сохраняет и роли. Для файлов подойдут Restic, Borg или Kopia — главное восстановить копию на тестовой машине.

Мониторинг на том же VPS полезен для метрик и логов, но он не сообщит о полной смерти машины. Например, Uptime Kuma проверяет HTTP, TCP, DNS и сертификаты, только не размещайте её рядом с наблюдаемыми сайтами. Заводите в ней монитор на каждый домен из списка, подключаете Телеграм-уведомления, и сообщения о падении соседей будут приходить автоматически, с задержкой, зависящей от настроек проверок. Также не забывайте смотреть на потребление машины в целом.

Когда пора расселять

Среди логичных причин разъехаться: 

  • разные владельцы или уровни доверия;

  • ресурсы, когда один сервис регулярно мешает остальным даже после настройки лимитов;

  • деньги и обещания клиентам — если простой одного приложения останавливает оплату в другом, общая машина не поможет сэкономить. 

Также обратите внимание на требования к обновлениям. Если одному проекту нужно часто менять Docker и ядро, а другой должен месяцами работать без перезагрузок, лучше развести. 

Если упёрлись только в память или диск, второй сервер тоже не всегда нужен. Иногда проще увеличить текущий. У RUVDS параметры VPS можно менять без переноса данных в пределах доступных конфигураций, а VPS с 2 vCPU, 4 ГБ RAM и 40 ГБ SSD стоит 1149 рублей в месяц при оплате сразу за три месяца. Такой машины хватит для нескольких сервисов, если не запускать на ней очередной локальный ИИ и не считать 4 ГБ бесконечностью.

Делить лучше по последствиям

В итоге на одном VPS собирайте сервисы, которым вы доверяете одинаково и чей одновременный простой готовы пережить. Разводите сервисы прокси по доменам или контейнерами, ограничивайте каждый по памяти, считайте потребление с запасом, не смешивайте бой с полигоном, и одна средняя машина заменит вам четыре.

А сколько сервисов крутится на вашей основной VPS, и был ли случай, когда один сосед уронил остальных? Делитесь историями в комментариях.

© 2026 ООО «МТ ФИНАНС»

Комментарии (3)


  1. ex_ineris
    29.10.2026 15:16

    Я на своей VPS кручу mail server vpn server wire guard telega bot все это в докер контейнерах 2гб ОЗУ два ядра и 40гб ssd вполне хватает, можно что то ещё но пока не придумал, а и fail2ban что бы боты не лезли в бан на год по айпи банит, а самое главное не написал касательно темы сабжа, nginx proxy manager рулит и claud flare с CNAME.


    1. andreymal
      29.10.2026 15:16

      fail2ban что бы боты не лезли в бан на год по айпи банит

      Хм, а какой у вас banaction? Я у себя поставил бан всего 7 дней, но этого уже достаточно, чтобы systemctl stop fail2ban задумывался на несколько минут и тем самым создавал проблемы


      1. dmitrychou
        29.10.2026 15:16

        atlas@mail:~$ sudo fail2ban-client status sshd | egrep “Currently banned|Total banned” |- Currently banned: 11649 |-
        Total banned: 11649
        atlas@mail:~$


        Вообще не создает проблем, наооборот из-за постояннного перебора и флуда на порты, были проблемы, сейчас с fail2ban и блоком все гуд.