Установка и развёртка за nginx
От чистого Ubuntu до работающего HTTPS-сайта: сборка, PostgreSQL, внешняя конфигурация, systemd, nginx и обязательные правки настроек по умолчанию.
Инструкция рассчитана на Ubuntu 22.04/24.04 или Debian 12 и одну машину, на которой живут и приложение, и база, и nginx. Для другого дистрибутива меняются только имена пакетов.
Внимание
Настройки в application.properties в репозитории — девелоперские. Там
путь к файлам с чужой рабочей машины, пароль администратора в открытом виде и
порт 80, который конфликтует с nginx. Раздел
«Что обязательно поменять» — не рекомендация,
а условие работоспособности.
Итоговая схема#
1. Зависимости#
sudo apt update && sudo apt install -y openjdk-21-jdk-headless postgresql nginx git
Spring Boot 4 требует Java 17 как минимум; в pom.xml указано
<java.version>17</java.version>, но собирать и запускать можно на любой более
новой LTS — Java 21 предпочтительнее из-за производительности сборщика мусора.
Проверьте, что версия действительно подхватилась:
java -version
2. База данных#
sudo -u postgres psql -c "CREATE USER otryad WITH PASSWORD 'ЗАМЕНИТЕ_МЕНЯ';"
sudo -u postgres psql -c "CREATE DATABASE otryadwebsite OWNER otryad;"
Схему создавать руками не нужно: в проекте стоит
spring.jpa.hibernate.ddl-auto=update, и Hibernate сам построит таблицы при
первом запуске.
Осторожно
ddl-auto=update умеет добавлять столбцы и таблицы, но никогда не удаляет и
не сужает существующие. После обновлений, которые переименовывают поля,
в базе останутся старые столбцы — это безопасно, но за схемой нужно следить
руками. Перед каждым обновлением делайте pg_dump.
3. Сборка#
Обратите внимание: в репозитории Maven-проект лежит во вложенной папке
OtryadWebsite/, а не в корне. Собирать нужно оттуда.
git clone https://github.com/Electronprod/OtryadWebsite.git /tmp/otryad-src
cd /tmp/otryad-src/OtryadWebsite && ./mvnw -B clean package -DskipTests
Результат — target/OtryadWebsite-1.0.jar, исполняемый fat jar. Lombok и
DevTools в него не попадают: первый исключён в настройках
spring-boot-maven-plugin, второй помечен optional и Spring Boot не
упаковывает его в собранный архив.
Раскладываем по местам:
sudo useradd --system --home /var/lib/otryadwebsite --shell /usr/sbin/nologin otryad
sudo mkdir -p /opt/otryadwebsite /var/lib/otryadwebsite/media /etc/otryadwebsite
sudo cp target/OtryadWebsite-1.0.jar /opt/otryadwebsite/app.jar
sudo chown -R otryad:otryad /var/lib/otryadwebsite
4. Что обязательно поменять#
Файл application.properties внутри jar трогать не нужно — Spring Boot
подхватывает внешний файл и он перекрывает встроенный. Создайте
/etc/otryadwebsite/application.properties:
# --- порт: 80 занят nginx ---
server.port=8080
server.address=127.0.0.1
# --- за обратным прокси: иначе host в sitemap и JSON-LD будет неправильным ---
server.forward-headers-strategy=native
# --- файлы ---
system.storage.path=/var/lib/otryadwebsite/media/
web.storage.url-prefix=/media/
web.storage.local-serving=false
# --- размеры загрузки: согласуйте с client_max_body_size в nginx ---
spring.servlet.multipart.max-file-size=2GB
spring.servlet.multipart.max-request-size=2GB
# --- база ---
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/otryadwebsite
spring.datasource.username=otryad
spring.datasource.password=ЗАМЕНИТЕ_МЕНЯ
# --- первый администратор: используется только если админов в базе ещё нет ---
security.admin.login=ЗАМЕНИТЕ_МЕНЯ
security.admin.password=ЗАМЕНИТЕ_МЕНЯ8
# --- выключить девелоперский переключатель ---
dev=false
Что здесь важно и почему:
| Параметр | В репозитории | Почему нужно менять |
|---|---|---|
server.port | ${SERVER_PORT:80} | 80 занят nginx — приложение не поднимется |
system.storage.path | C:/Users/Electron/Downloads/ | путь с рабочей машины разработчика |
web.storage.local-serving | true | при true файлы отдаёт Spring, а не nginx; вся выгода от прокси теряется |
server.forward-headers-strategy | закомментирован | без него приложение считает себя доступным по http://127.0.0.1:8080, и этот адрес попадает в sitemap.xml, og:url и JSON-LD |
security.admin.password | nadezda123 | пароль администратора по умолчанию лежит в публичном репозитории |
spring.servlet.multipart.max-file-size | 10GB | вместе с лимитом nginx определяет реальный максимум; 10 ГБ в одном запросе почти всегда ошибка |
dev | true | флаг девелоперского режима — в продакшене ему нечего делать включённым |
Важно
Пароль администратора должен пройти валидацию сущности Person: минимум 8
символов, хотя бы одна латинская буква и хотя бы одна цифра. Иначе
приложение стартует, но создать первого админа не сможет и упадёт с ошибкой
валидации. Пара security.admin.* читается только когда в таблице users
нет ни одного пользователя с ролью ROLE_ADMIN — после первого запуска
менять её бессмысленно, пароль меняется через панель.
5. systemd#
Критичная деталь: настройки сайта хранятся в файле properties.json, и путь к
нему в коде относительный — файл создаётся в текущем рабочем каталоге
процесса. Поэтому WorkingDirectory обязателен: без него файл окажется в /
или в каталоге, откуда служба была запущена, и после переезда все настройки,
SEO и короткие ссылки «исчезнут».
/etc/systemd/system/otryadwebsite.service:
[Unit]
Description=OtryadWebsite
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=otryad
Group=otryad
WorkingDirectory=/var/lib/otryadwebsite
ExecStart=/usr/bin/java -Xmx512m -jar /opt/otryadwebsite/app.jar \
--spring.config.additional-location=file:/etc/otryadwebsite/
Restart=on-failure
RestartSec=5
SuccessExitStatus=143
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/otryadwebsite
[Install]
WantedBy=multi-user.target
Права на файл с паролями и запуск:
sudo chmod 640 /etc/otryadwebsite/application.properties && sudo chown root:otryad /etc/otryadwebsite/application.properties
sudo systemctl daemon-reload && sudo systemctl enable --now otryadwebsite
Проверка, что поднялось:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
Логи — journalctl -u otryadwebsite -f. В них при первом запуске должно быть
Administrator profile not found, creating a new one....
6. nginx#
/etc/nginx/sites-available/otryadwebsite:
server {
listen 80;
server_name example.ru www.example.ru;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.ru www.example.ru;
ssl_certificate /etc/letsencrypt/live/example.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.ru/privkey.pem;
# должно быть не меньше spring.servlet.multipart.max-file-size
client_max_body_size 2G;
# приложение сжимает только html/xml/json — css, js и svg остаются на nginx
gzip on;
gzip_types text/css application/javascript image/svg+xml application/json;
gzip_min_length 1024;
# медиафайлы: отдаём с диска, Java не трогаем
location /media/ {
alias /var/lib/otryadwebsite/media/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# телеметрия наружу не нужна, хотя она и закрыта авторизацией
location /actuator/ { deny all; }
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Host $host;
# загрузка больших видео из панели
proxy_request_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
Включаем и проверяем конфигурацию:
sudo ln -s /etc/nginx/sites-available/otryadwebsite /etc/nginx/sites-enabled/ && sudo nginx -t && sudo systemctl reload nginx
Сертификат:
sudo apt install -y certbot python3-certbot-nginx && sudo certbot --nginx -d example.ru -d www.example.ru
Почему именно так#
alias, а не root. Публичные адреса файлов формируются как
web.storage.url-prefix + batchId + имя файла, то есть /media/<uuid>/01.jpg.
На диске это <system.storage.path>/<uuid>/01.jpg. alias отбрасывает
префикс /media/, root бы его сохранил и получилось бы
/var/lib/otryadwebsite/media/media/<uuid>/01.jpg. Слэши на конце
у location и alias обязательны — и оба, и одновременно.
Загрузка всё равно идёт через Java. nginx отдаёт медиа только на чтение.
POST /dashboard/media/upload обрабатывает Spring, поэтому лимит размера
нужен и в nginx (client_max_body_size), и в приложении
(spring.servlet.multipart.*). Меньший из двух и будет реальным
ограничением; при превышении лимита nginx вернёт 413.
X-Forwarded-* без forward-headers-strategy бесполезны. Приложение
собирает абсолютные адреса через ServletUriComponentsBuilder — они попадают
в sitemap.xml, в Open Graph и в JSON-LD. Без native там окажется
внутренний адрес и схема http, и поисковики проиндексируют ссылки, которые
никуда не ведут.
7. После установки#
- Зайдите на
https://example.ru/authи войдите парой изsecurity.admin.*. Сразу же смените пароль в/dashboard/usersи заведите отдельные учётные записи модераторам. - Включите режим защиты в
/dashboard, пока наполняете сайт: публичная часть закроется для всех, кроме сотрудников и владельцев ссылки с токеном. - Замените домен в
robots.txt— там прописанSitemap: https://otryad-nadezhda.ru/sitemap.xml. Файл лежит вsrc/main/resources/static/, то есть правится в исходниках и требует пересборки. Альтернатива без пересборки — перехватить/robots.txtотдельнымlocationв nginx. - В
/dashboard/seoпройдитесь по заголовкам и описаниям: значения по умолчанию написаны про отряд «Надежда». - Проверьте
https://example.ru/sitemap.xml— адреса внутри должны начинаться с вашего домена иhttps. Если нет, не применилсяserver.forward-headers-strategy.
8. Резервное копирование#
Состояние сайта — это три независимых вещи, и потеря любой из них теряет контент:
#!/bin/sh
# /usr/local/bin/otryad-backup.sh
set -eu
DST=/var/backups/otryad/$(date +%F)
mkdir -p "$DST"
sudo -u postgres pg_dump otryadwebsite | gzip > "$DST/db.sql.gz"
cp /var/lib/otryadwebsite/properties.json "$DST/"
tar czf "$DST/media.tar.gz" -C /var/lib/otryadwebsite media
properties.json маленький, но восстановить его нельзя ничем: там SEO для всех
страниц, короткие ссылки, контакты, соцсети и токен доступа.
9. Обновление версии#
cd /tmp/otryad-src && git pull && cd OtryadWebsite && ./mvnw -B clean package -DskipTests
sudo -u postgres pg_dump otryadwebsite | gzip > /var/backups/otryad-pre-upgrade.sql.gz
sudo systemctl stop otryadwebsite
sudo cp target/OtryadWebsite-1.0.jar /opt/otryadwebsite/app.jar
sudo systemctl start otryadwebsite
Простой на несколько секунд неизбежен: приложение одно, сессии хранятся в памяти, поэтому вошедших в панель после перезапуска разлогинит.
Частые проблемы#
| Симптом | Причина |
|---|---|
Служба падает при старте, в логах Address already in use | не переопределён server.port, приложение пытается занять 80 |
| Все настройки сайта сбросились после перезапуска | не задан WorkingDirectory — properties.json создался в другом каталоге |
| Картинки в альбоме не открываются, 404 | несогласованы system.storage.path и alias, либо потерян слэш на конце одного из них |
| Альбом открывается, но пустой | в каталоге batchId нет файлов, кроме preview.jpg — из галереи он исключается всегда |
В sitemap.xml адреса вида http://127.0.0.1:8080/... | не применился server.forward-headers-strategy=native |
| Загрузка большого видео обрывается на 413 | client_max_body_size меньше размера файла |
| Загрузка обрывается на 500 при большом файле | лимит nginx подняли, а spring.servlet.multipart.max-file-size нет |
| Вход в панель отдаёт 429 с JSON | сработал лимит попыток: 5 в минуту с адреса, 5 в минуту на логин, 30 в сутки с адреса |
/dashboard отдаёт 403 после входа | у пользователя роль не ROLE_ADMIN и не ROLE_MODERATOR |