Java работает в IntelliJ, но не на сервере: что проверить
Сценарий знакомый: в IntelliJ IDEA нажимаем Run, приложение стартует, подключается к базе и отвечает на запросы. После сборки тот же проект переносим на Linux-сервер, запускаем JAR и получаем совсем другую картину: процесс сразу завершается, база недоступна, порт 8080 не отвечает или Nginx возвращает 502.
Обычно искать нужно не «особенность сервера», а различия между окружениями. IntelliJ уже знает, какую JDK использовать, из какого каталога запускать проект, какие переменные среды передать процессу и какой профиль активировать. На сервере все это приходится задавать явно. Поэтому удобнее идти изнутри наружу: JVM, JAR, конфигурация, внешние сервисы, порт приложения, firewall, reverse proxy и только затем домен.
Почему Run в IntelliJ и запуск на сервере - это разные окружения
Успешный Run в IntelliJ подтверждает работоспособность приложения только в том окружении, которое использует IDE. Это еще не означает, что сервер запускает тот же код в тех же условиях.
Run Configuration может задавать JDK, VM options, Program arguments, Environment variables, Working directory и активный Spring profile. IDE также формирует classpath и сразу показывает stack trace. На сервере часть этих настроек может просто отсутствовать.
Перед поиском ошибки полезно открыть Run Configuration и выписать:
- какая JDK используется;
- какой Main class или JAR запускается;
- какие VM options и Program arguments указаны;
- какие переменные окружения заданы;
- какая рабочая директория используется;
- какой Spring profile активирован.
Например, SPRING_PROFILES_ACTIVE=dev может быть прописана только в IntelliJ. Локально приложение получает нужный профиль, а на сервере переменной нет и настройки меняются. Сначала сравниваем окружения. Если они отличаются сразу по нескольким параметрам, править контроллер или SQL-запрос рано.
JAR не стартует: сначала сравните версии Java
UnsupportedClassVersionError обычно означает, что JAR собран для более новой версии Java, чем та, которой его пытаются запустить. Типичный случай: проект собирали на Java 21, а на сервере фактически используется Java 17.
Начните с проверки:
java -version
javac -version Для запуска готового JAR наличие javac не обязательно. Эта команда полезна прежде всего при проверке среды, если проект собирают непосредственно на сервере. Для Maven можно дополнительно выполнить:
mvn -version
which java
echo $JAVA_HOME mvn -version показывает JVM, на которой работает Maven, но не доказывает, под какую версию Java компилируется проект. Если версии на первый взгляд совпадают, проверьте release/target в Maven или Java toolchain/targetCompatibility в Gradle.
Есть и менее очевидный вариант: в SSH команда java указывает на одну JDK, а systemd запускает сервис через другую. Поэтому вопрос должен звучать не «Java на сервере установлена?», а «какую JVM реально использует этот процесс?».
Ставить самую новую JDK не требуется. Нужна совместимая версия. Пока JAR падает на несовместимой JVM, Nginx, DNS и firewall можно не трогать.
Проверьте JAR до настройки Nginx, домена и SSL
До настройки Nginx запустите собранный JAR непосредственно на сервере через java -jar. Если он падает в консоли, reverse proxy пока диагностировать бессмысленно.
Для Maven сборка обычно выглядит так:
mvn clean package Для Gradle:
./gradlew build Затем проверьте, какой файл появился в target/ или build/libs/. Команды ls -lh и pwd помогают быстро заметить старый JAR, неправильный каталог или запуск не того артефакта.
Сам запуск:
java -jar app.jar Если вместо старта появляется no main manifest attribute, проблема в структуре или способе сборки JAR. Если процесс завершается через пару секунд со stack trace, ищем первичную ошибку там. 502 от Nginx в этот момент всего лишь симптом: проксировать уже некуда.
После такого теста уже понятно, где копать дальше: в сборке приложения или за пределами Java. Только когда JAR стабильно работает из терминала, имеет смысл добавлять сеть, Nginx и домен.
Локальный .env, application.yml и секреты на сервер сами не переедут
Если JAR стартует, но не может создать DataSource, отправить почту или обратиться к внешнему API, здесь уже стоит смотреть не на сборку, а на настройки, с которыми приложение запустилось.
Какие переменные обычно теряются при переносе
Часто это DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, API_KEY, SMTP_HOST или JWT_SECRET. Проверить окружение можно так:
printenv
env
echo $VARIABLE_NAME Реальные пароли и токены не нужно выводить в публичные логи. Достаточно убедиться, что переменная существует и доступна именно процессу Java.
Переменная может быть видна в SSH-сессии, но отсутствовать у systemd. Для сервиса окружение часто задают через Environment= или EnvironmentFile=.
Что проверить в Spring Boot profiles
В Spring Boot стоит проверить application.properties, application.yml, application-prod.yml и значение spring.profiles.active. Сам по себе файл .env не означает, что Spring Boot автоматически его загрузит: это зависит от способа запуска, подключенной библиотеки или внешнего механизма, который передает значения в окружение.
Рабочая директория тоже влияет на результат. Путь ./config/app.yml зависит от каталога запуска: IntelliJ может использовать корень проекта, а systemd - другой WorkingDirectory.
Если конфиг существует только внутри Run Configuration, для сервера его фактически нет. В таких случаях повторная сборка JAR обычно ничего не меняет.
localhost на сервере может указывать уже не туда
localhost означает среду, внутри которой выполняется конкретный процесс. Поэтому адрес, правильный на ноутбуке, после переноса на сервер может вести уже не к тому сервису.
Например, локально приложение использует:
jdbc:mysql://localhost:3306/shop Это работает, если MySQL установлен на том же компьютере. После переноса Java на сервер localhost означает уже сервер. База при этом может жить на другой VM, в облачной БД или в Docker-контейнере. То есть localhost не «сломался» - после переноса это просто другой localhost.
Симптомы тоже различаются: Connection refused, Connection timed out, ошибка аутентификации. Проверять нужно фактический host, port, listener, firewall и учетные данные, а не абстрактную «сеть».
В Docker путаница встречается еще чаще: localhost внутри контейнера относится к самому контейнеру. Redis в другом контейнере по адресу localhost:6379 может быть недоступен.
Для проверки соединения пригодятся ss, nc, curl, а при наличии клиентов - mysql или psql. Практический вопрос один: к какому реальному адресу должен подключаться Java-процесс и отвечает ли сервис именно там.
Java запущена, но приложение недоступно извне
Работающий JVM-процесс еще не означает, что HTTP-сервис доступен из Интернета. Приложение может слушать только 127.0.0.1, работать на другом server.port, быть закрыто firewall или уже завершиться после сообщения о старте.
Проверяем, слушает ли Java нужный порт
ss -lntp Без повышенных прав информация о PID и процессе на некоторых системах может быть неполной, поэтому при необходимости используют sudo ss -lntp. Если Spring Boot пишет, что запустился на 8080, а система показывает 127.0.0.1:8080, прямое внешнее подключение к этому порту работать не будет.
Есть LISTEN - идем дальше. Нет LISTEN - браузер и внешний firewall пока можно не трогать.
Проверяем сервис изнутри сервера
curl http://127.0.0.1:8080 Если приложение отвечает локально, Java и HTTP-слой уже работают хотя бы внутри сервера. При необходимости затем проверяют серверный адрес:
curl http://SERVER_IP:8080 Порядок простой: процесс -> LISTEN -> localhost -> серверный IP -> внешний клиент.
Проверяем firewall
Если сервис отвечает локально, но недоступен извне, проверяют firewall системы и внешний фильтр хостинга или облачной платформы. Для UFW можно начать с ufw status.
При этом открывать 8080 наружу требуется не всегда. Если Java должна работать только за Nginx, нормальная схема - Nginx на 80/443 и приложение на 127.0.0.1:8080. В таком варианте внешний доступ к 8080 специально не нужен.
IP:8080 работает, а домен нет: проверяем DNS и reverse proxy
Если приложение отвечает напрямую по IP и порту, Java уже, скорее всего, работает. Следующая зона проверки - DNS, virtual host, reverse proxy, upstream и SSL. Backend отвечает напрямую - Java пока оставляем в покое.
Если 8080 отвечает, идем от домена к backend: проверяем, на какой IP резолвится имя через nslookup или dig, затем запускаем nginx -t, смотрим конфигурацию virtual host и убеждаемся, что upstream указывает на реальный адрес приложения.
Например:
proxy_pass http://127.0.0.1:8080; Если Java слушает 8080, а в proxy_pass указан 8081, Nginx закономерно вернет 502. Но сам код 502 еще не доказывает, что Java остановлена: в error log Nginx нужно посмотреть, что произошло с upstream - connection refused, timeout, reset by peer или другая ошибка.
Если проект переносится в отдельную постоянно работающую Linux-среду, например на Linux VDS для Java-приложения, версию JDK, способ запуска и сетевую схему уже приходится задавать на стороне сервера. Поэтому смена среды сама по себе не исправляет конфигурацию: сначала проверяют Java локально на сервере, затем upstream и только потом внешний домен.
Если HTTP работает, а HTTPS нет, проверяют SSL и нужный virtual host. Если curl к backend успешен, правки Java-кода здесь обычно только уводят в сторону.
Приложение исчезает после SSH или падает через несколько минут
Если Java-приложение исчезает после закрытия SSH, его нужно отделить от интерактивной сессии и запускать как сервис. Если процесс живет дольше, но затем падает сам, первым источником ответа становятся application log и системный журнал.
Почему процесс исчезает после SSH
java -jar app.jar в обычной SSH-сессии удобен для проверки, но не для постоянной работы production-сервиса. Для этого обычно используют systemd или другой менеджер сервисов: процесс не зависит от терминала, может стартовать после перезагрузки и вести журнал.
Проверки:
systemctl status имя-сервиса
journalctl -u имя-сервиса -n 100 --no-pager
systemctl cat имя-сервиса systemctl cat особенно полезен в контексте переноса из IntelliJ: можно увидеть ExecStart, EnvironmentFile и WorkingDirectory и сравнить их с Run Configuration.
Если сервис нужно перезапустить:
systemctl restart имя-сервиса Постоянный restart еще не означает, что сервис восстановился. Иногда это просто цикл: падение -> restart -> новое падение. Причину завершения JVM все равно нужно читать в журнале.
Что смотреть, если JVM падает сама
Среди реальных причин - OutOfMemoryError, нехватка RAM, слишком жесткие параметры heap, OOM Killer, потеря соединения с БД, занятый порт при повторном старте, Permission denied, отсутствующий файл или неправильный путь.
free -h
ps
journalctl
dmesg dmesg полезен при подозрении, что процесс завершила сама система, но чтение журнала может требовать дополнительных прав. Начинать с предположения «мало памяти» не стоит: сначала нужен след - exception, сообщение OOM, запись journal или другое подтверждение.
Отдельно проверьте права и пути. systemd может запускать Java от пользователя, у которого нет доступа к каталогу, сертификату, конфигу или директории для записи. Такие проблемы обычно быстро видны в логах.
Диагностический маршрут по симптомам
Чтобы не менять пять настроек одновременно, привязывайте следующий шаг к конкретному симптому. Так быстрее видно, на каком слое приложение перестало работать.
| Симптом | Первая проверка | Следующий шаг |
|---|---|---|
| JAR вообще не стартует |
| Проверить JVM, артефакт и команду запуска |
|
| Версия Java | Сопоставить версию сборки и runtime |
|
| Способ сборки JAR | Проверить main class и конфигурацию упаковки |
| Локально работает, на сервере сразу падает | ENV, profile, config, paths | Сравнить Run Configuration и сервер |
| Не подключается к БД | host, port, credentials | Проверить listener, firewall и маршрут |
|
| Где реально находится сервис | Исправить адрес или сетевую схему |
| Java работает, браузер не подключается |
| Проверить bind address и firewall |
| IP:8080 работает, домен нет | DNS и Nginx upstream | Проверить |
| После выхода из SSH приложение исчезает | Способ запуска | Запустить как сервис через systemd |
| Приложение быстро падает | Exception и journal | Проверить RAM, JVM, права, файлы и БД |
| Nginx возвращает 502 | Backend и Nginx error log | Проверить upstream, Java-процесс и порт |
Перед изменением конфигурации полезно пройти короткий чек-лист:
- Какая Java используется локально?
- Какая Java реально запускает приложение на сервере?
- Собран ли именно тот JAR, который сейчас запускается?
- Стартует ли
java -jarбез Nginx и домена? - Все ли environment variables доступны процессу?
- Активирован ли нужный Spring profile?
- Правильная ли рабочая директория?
- Существуют ли требуемые файлы и пути?
- Верно ли указаны host и port для БД, Redis и внешних API?
- Слушает ли приложение ожидаемый порт?
- Что показывает
curlс самого сервера? - Если backend отвечает, правильно ли настроен reverse proxy?
- Не зависит ли процесс от открытой SSH-сессии?
- Что написано в exception, application log и
journalctl?
Практический порядок получается таким: JVM -> JAR -> конфигурация -> зависимые сервисы -> порт Java -> firewall -> reverse proxy -> DNS и SSL -> внешний клиент. Если двигаться по этой цепочке, становится видно не только что сломалось, но и где именно. Хаотично менять JDK, Nginx, firewall и код одновременно почти всегда хуже: после этого трудно понять, какое действие действительно исправило проблему.
Комментарии