운영 중인 WAS와 DB 서버를 신규 서버로 이전했습니다. 기존 서버에는 Tomcat 기반 WAS와 MariaDB가 함께 설치되어 있었으며, 외부 요청을 처리하기 위한 Nginx도 같은 서버에서 운영되고 있었습니다.
이번 작업은 단순히 Java 애플리케이션의 WAR 파일만 옮기는 것이 아니었습니다. MariaDB 데이터와 계정 권한, Tomcat 실행 환경, Spring 애플리케이션 설정, Nginx Reverse Proxy, 방화벽 및 DNS까지 기존 서버의 전체 서비스 구성을 신규 서버에 재구성해야 했습니다.
이번 글에서는 실제 회사명, 도메인, IP 주소, DB명과 계정 정보는 제외하고, WAS와 DB가 함께 구성된 운영 서버를 신규 서버로 이전한 과정과 작업 중 발생한 문제를 단계별로 정리해 보겠습니다.
기존 서버에는 다음 구성요소가 모두 함께 설치되어 있었습니다.
Nginx
Tomcat
MariaDB
Java(Spring) 애플리케이션
즉, WAS와 DB가 분리된 구조가 아니라 하나의 서버에서 웹 서비스와 데이터베이스가 함께 동작하는 구조였습니다.
이번 작업의 목표는 기존 서버의 서비스 구성을 신규 서버로 그대로 이전한 뒤, 마지막에 DNS를 변경하여 사용자가 신규 서버로 접속하도록 전환하는 것이었습니다.
처음에는 다음 정도의 작업으로 끝날 것으로 예상했습니다.
DB 백업
→ 신규 서버 DB 복원
→ Tomcat 이전
→ WAR 배포
→ Nginx 설정
→ DNS 변경
하지만 실제 운영 서버 이전 작업은 단순한 파일 복사만으로 끝나지 않았습니다.
MariaDB 설치와 계정 권한, JDBC 설정, Tomcat 포트, 방화벽, Nginx Reverse Proxy, DNS 전파, 클라이언트 DNS 캐시까지 여러 요소를 순서대로 확인해야 했습니다.
이번 글에서는 실제 회사명, 도메인, IP 주소, DB명, 계정 정보 등 민감한 운영 정보는 모두 제외하고, 전체 서버 이전 과정과 중간에 발생했던 문제를 단계별로 정리해 보겠습니다.
1. 기존 서버 구성부터 확인했습니다
서버를 이전하기 전에 가장 먼저 해야 할 일은 기존 서버가 어떤 구조로 운영되고 있는지 파악하는 것입니다.
이번 시스템은 다음과 같은 구조였습니다.
사용자
↓
도메인
↓
Nginx
↓
Tomcat
↓
MariaDB
Nginx가 외부 요청을 받은 뒤 내부의 Tomcat 포트로 요청을 전달하는 Reverse Proxy 구조였습니다.
Tomcat에서 실행되는 Spring 애플리케이션은 같은 서버에 설치된 MariaDB에 접속하고 있었습니다.
따라서 신규 서버에서 동일한 서비스를 구성하려면 다음 항목을 모두 확인하고 이전해야 했습니다.
- MariaDB 데이터
- MariaDB 사용자 계정과 권한
- Java 실행 환경
- Tomcat
- 애플리케이션 WAR 파일
- Spring DB 설정
- Nginx 설정
- 방화벽 설정
- DNS A 레코드
기존 서버에서 실행 중인 Java 프로세스는 다음 명령으로 확인했습니다.
ps -ef | grep java
Tomcat이 여러 개 운영되는 환경에서는 각 프로세스의 catalina.base, 실행 포트, 서비스 경로를 반드시 구분해야 합니다.
Java 프로세스 정보에 다음과 같은 내용이 포함되어 있다면 실제 Tomcat 경로를 확인할 수 있습니다.
-Dcatalina.base=/home/service/server
-Dcatalina.home=/home/service/server
현재 사용 중인 포트는 다음 명령으로 확인했습니다.
ss -lntp
Java 프로세스가 사용하는 포트만 확인하려면 다음과 같이 조회할 수 있습니다.
ss -lntp | grep java
MariaDB가 사용하는 포트도 함께 확인했습니다.
ss -lntp | grep mysqld
이 단계에서 기존 서버의 디렉터리 구조, Java 버전, Tomcat 버전, 실행 포트, DB 포트, 계정, 로그 경로 등을 미리 정리해 두는 것이 중요합니다.
기존 서버 구성을 정확히 파악하지 않은 상태에서 신규 서버부터 구성하면, 나중에 어떤 설정이 누락되었는지 확인하기 어려워질 수 있습니다.
2. 신규 서버의 기본 환경을 확인했습니다
신규 서버에서는 먼저 운영체제와 기본 사양을 확인했습니다.
운영체제 버전은 다음 명령으로 확인했습니다.
cat /etc/redhat-release
Java 버전은 다음과 같이 확인했습니다.
java -version
디스크 용량도 확인했습니다.
df -h
메모리 사용량은 다음 명령으로 확인했습니다.
free -h
현재 열려 있는 포트도 확인했습니다.
ss -lntp
운영 서버 이전 시에는 기존 서버와 신규 서버의 Java 버전 차이를 반드시 확인해야 합니다.
특히 오래된 Spring Framework나 JDBC 드라이버를 사용하는 프로젝트는 최신 Java 버전에서 실행되지 않을 수 있습니다.
기존 애플리케이션이 Java 8을 기준으로 개발되어 있다면 신규 서버에도 동일한 Java 버전을 설치하는 것이 가장 안전합니다.
Tomcat도 가능하면 기존 서버와 같은 버전을 사용하는 것이 좋습니다.
운영 서버 이전 작업에서는 새로운 버전으로 업그레이드하는 것보다 기존 환경을 동일하게 재현하는 것이 우선입니다.
Java나 Tomcat 버전 업그레이드는 서버 이전이 완료된 뒤 별도의 작업으로 진행하는 편이 문제 원인을 구분하기 쉽습니다.
3. 신규 서버에 MariaDB를 설치했습니다
신규 서버에 MariaDB를 설치했습니다.
CentOS 계열 운영체제에서는 다음과 같이 설치할 수 있습니다.
yum install -y mariadb-server
설치가 완료된 뒤 MariaDB 서비스를 시작했습니다.
systemctl start mariadb
서버가 재부팅된 뒤에도 자동으로 실행되도록 설정했습니다.
systemctl enable mariadb
MariaDB 실행 상태는 다음 명령으로 확인했습니다.
systemctl status mariadb
서비스가 정상적으로 실행되었다면 포트를 확인했습니다.
ss -lntp | grep mysqld
이번 시스템은 MariaDB 기본 포트가 아닌 별도의 포트를 사용하고 있었습니다.
따라서 MariaDB 설정 파일에서 기존 운영 환경과 동일한 포트로 변경했습니다.
예시는 다음과 같습니다.
[mysqld]
port=3307
실제 운영 포트는 보안상 글에 공개하지 않고 예시 포트로 작성했습니다.
포트를 변경한 뒤 MariaDB를 재기동했습니다.
systemctl restart mariadb
변경한 포트가 정상적으로 적용되었는지 다시 확인했습니다.
ss -lntp | grep 3307
MariaDB가 정상적으로 실행되지 않는 경우에는 다음 명령으로 로그를 확인할 수 있습니다.
journalctl -u mariadb
또는 MariaDB 로그 파일이 별도로 지정되어 있다면 해당 로그를 확인해야 합니다.
4. 데이터베이스와 사용자 계정을 생성했습니다
MariaDB 설치가 완료된 뒤 애플리케이션에서 사용할 데이터베이스를 생성했습니다.
CREATE DATABASE service_db
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
애플리케이션 전용 사용자도 생성했습니다.
CREATE USER 'service_user'@'localhost'
IDENTIFIED BY '안전한_비밀번호';
생성한 사용자에게 데이터베이스 권한을 부여했습니다.
GRANT ALL PRIVILEGES
ON service_db.*
TO 'service_user'@'localhost';
권한 설정을 적용했습니다.
FLUSH PRIVILEGES;
MariaDB에서는 사용자명뿐만 아니라 접속 Host도 함께 구분합니다.
다음 계정들은 서로 다른 계정으로 취급될 수 있습니다.
'service_user'@'localhost'
'service_user'@'127.0.0.1'
'service_user'@'%'
애플리케이션이 127.0.0.1로 접속하는데 MariaDB에는 service_user@localhost만 생성되어 있다면 인증 문제가 발생할 수 있습니다.
환경에 따라 localhost와 127.0.0.1의 처리 방식이 다를 수 있기 때문에 실제 애플리케이션의 JDBC URL과 MariaDB 사용자 Host 조건을 함께 확인해야 합니다.
현재 생성된 사용자는 다음 쿼리로 확인할 수 있습니다.
SELECT User, Host
FROM mysql.user
ORDER BY User, Host;
사용자에게 부여된 권한은 다음과 같이 확인했습니다.
SHOW GRANTS FOR 'service_user'@'localhost';
보안을 고려하면 무조건 ‘%’ 권한을 부여하기보다 실제 접속 방식에 맞는 Host만 허용하는 것이 좋습니다.
이번 구성은 WAS와 DB가 같은 서버에서 실행되기 때문에 외부 IP에서 DB에 접속할 필요가 없었습니다.
따라서 애플리케이션이 내부 주소로 접속하도록 설정했습니다.
5. 기존 MariaDB 데이터를 백업했습니다
기존 서버의 데이터베이스를 백업했습니다.
DB 백업은 GUI 도구를 이용할 수도 있고 mysqldump명령을 사용할 수도 있습니다.
명령어로 백업하는 경우에는 다음과 같이 진행할 수 있습니다.
mysqldump \
-u service_user \
-p \
-P 3307 \
--routines \
--triggers \
--events \
service_db > service_db_backup.sql
운영 DB에는 단순히 테이블과 데이터만 존재하는 것이 아닐 수 있습니다.
다음 항목도 함께 사용하는지 확인해야 합니다.
- View
- Trigger
- Procedure
- Function
- Event
- Index
Stored Procedure나 Function을 사용하는 시스템이라면 –rountines 옵션이 필요합니다.
Trigger를 사용하는 경우에는 –triggers 옵션을 확인해야 합니다.
MariaDB Event Scheduler를 사용하는 경우에는 –events 옵션도 포함해야 합니다.
GUI 도구로 Export할 경우에도 테이블 데이터만 선택하지 말고 View, Trigger, Procedure 등이 포함되는지 확인하는 것이 좋습니다.
백업 파일이 생성된 뒤에는 파일 크기를 확인했습니다.
ls -lh service_db_backup.sql
파일이 지나치게 작거나 0바이트라면 정상적으로 백업되지 않았을 가능성이 있습니다.
백업 과정에서 오류가 발생하지 않았는지도 함께 확인했습니다.
6. 신규 서버에 DB를 복원했습니다
기존 서버에서 생성한 백업 파일을 신규 서버로 전달한 뒤 복원했습니다.
mysql \
-u service_user \
-p \
-P 3307 \
service_db < service_db_backup.sql
GUI 도구를 사용하는 경우 신규 서버 DB에 접속한 뒤 SQL 파일을 실행하는 방식으로 복원할 수도 있습니다.
복원이 완료된 뒤에는 테이블 목록을 확인했습니다.
SHOW TABLES;
주요 테이블의 데이터 건수도 기존 서버와 비교했습니다.
SELECT COUNT(*)
FROM 주요_테이블;
DB 전체 용량도 확인했습니다.
SELECT
table_schema,
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'service_db'
GROUP BY table_schema;
테이블만 복원되었다고 해서 DB 이전이 완료된 것은 아닙니다.
다음 항목도 함께 확인하는 것이 좋습니다.
SHOW FULL TABLES
WHERE Table_type = 'VIEW';
Procedure와 Function은 다음과 같이 확인할 수 있습니다.
SHOW PROCEDURE STATUS
WHERE Db = 'service_db';
SHOW FUNCTION STATUS
WHERE Db = 'service_db';
Trigger는 다음과 같이 확인할 수 있습니다.
SHOW TRIGGERS
FROM service_db;
복원 후에는 주요 코드 테이블, 사용자 정보, 운영 데이터가 정상적으로 들어왔는지 확인했습니다.
데이터베이스 복원 작업이 완료되었다고 바로 서비스를 전환하지 않고, 애플리케이션을 실행하기 전에 DB 자체 상태를 먼저 검증했습니다.
7. MariaDB 접속을 직접 테스트했습니다
애플리케이션을 실행하기 전에 신규 서버에서 MariaDB에 직접 접속했습니다.
mysql \
-h 127.0.0.1 \
-P 3307 \
-u service_user \
-p \
service_db
직접 접속이 정상적으로 이루어지는지 확인했습니다.
이 단계에서 접속이 실패한다면 아직 Tomcat이나 Spring 설정을 확인할 단계가 아닙니다.
먼저 다음 항목을 확인해야 합니다.
- MariaDB 서비스 실행 여부
- DB 포트
- 사용자명
- 비밀번호
- 사용자 Host 조건
- 데이터베이스명
- 사용자 권한
접속 오류 메시지에 따라 원인을 구분할 수 있습니다.
Access denied for user
이 메시지가 발생하면 사용자명, 비밀번호 또는 Host 권한을 확인해야 합니다.
Unknown database
이 메시지가 발생하면 DB명이 잘못되었거나 데이터베이스가 생성되지 않은 상태일 수 있습니다.
Can't connect to server
이 메시지가 발생하면 MariaDB가 실행되지 않았거나 포트가 잘못되었을 가능성이 있습니다.
CLI에서 DB 접속이 정상적으로 이루어진 뒤에 Tomcat과 Spring 설정을 진행했습니다.
8. 기존 Tomcat 환경을 신규 서버로 이전했습니다
다음으로 기존 서버의 Tomcat과 애플리케이션 파일을 신규 서버로 이전했습니다.
기존 Tomcat 디렉터리를 그대로 복사하는 방식으로 진행할 수도 있고, 신규 서버에 동일한 버전의 Tomcat을 설치한 뒤 필요한 설정과 WAR 파일만 옮기는 방식으로 진행할 수도 있습니다.
기존 환경과 최대한 동일하게 구성하기 위해 Tomcat 버전과 디렉터리 구조를 맞췄습니다.
Tomcat의 주요 디렉터리는 다음과 같습니다.
bin
conf
lib
logs
temp
webapps
work
특히 다음 파일과 경로를 확인했습니다.
conf/server.xml
conf/context.xml
conf/web.xml
bin/setenv.sh
webapps/ROOT.war
애플리케이션이 ROOT.war로 배포되어 있었다면 도메인의 루트 경로로 접속할 수 있습니다.
http://service.example.com/
WAR 파일명이 service.war라면 기본적으로 다음 경로로 배포됩니다.
http://service.example.com/service/
기존 서비스가 루트 경로로 운영되고 있었다면 신규 서버에서도 ROOT.war로 배포해야 기존 URL 구조를 유지할 수 있습니다.
Tomcat 디렉터리를 옮긴 뒤 파일 소유권도 확인했습니다.
chown -R 서비스계정:서비스그룹 /home/service/server
실행 스크립트에 실행 권한이 있는지도 확인했습니다.
chmod +x /home/service/server/bin/*.sh
Tomcat을 root 계정으로 실행하는 환경도 있지만, 보안을 고려하면 별도의 서비스 계정으로 실행하는 것이 좋습니다.
다만 기존 운영 환경을 이전하는 작업에서는 기존 실행 계정과 권한 구조를 먼저 그대로 재현한 뒤 개선 작업을 별도로 진행하는 편이 안전합니다.
9. Tomcat 포트를 확인했습니다
Tomcat의 HTTP Connector 포트는 server.xml에서 확인할 수 있습니다.
<Connector
port="8081"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
/>
글에서는 실제 운영 포트를 제외하고 예시 포트를 사용했습니다.
신규 서버에 여러 개의 Tomcat이 실행되는 경우에는 포트 충돌이 발생하지 않도록 확인해야 합니다.
Tomcat을 실행하기 전에 해당 포트를 다른 프로세스가 사용하고 있는지 확인했습니다.
ss -lntp | grep 8081
이미 다른 프로세스가 동일한 포트를 사용하고 있다면 Tomcat이 정상적으로 실행되지 않습니다.
Tomcat에서 사용하는 포트는 HTTP Connector 포트 외에도 Shutdown 포트와 AJP 포트가 있을 수 있습니다.
여러 Tomcat을 한 서버에서 운영하는 경우에는 server.xml 내 모든 포트가 중복되지 않도록 확인해야 합니다.
예를 들어 다음 항목을 확인해야 합니다.
<Server port="8005" shutdown="SHUTDOWN">
<Connector port="8081" protocol="HTTP/1.1" />
<Connector port="8009" protocol="AJP/1.3" />
사용하지 않는 AJP Connector는 비활성화하는 것도 고려할 수 있습니다.
10. Spring DB 설정을 신규 서버에 맞게 변경했습니다
Tomcat과 WAR 파일을 이전한 뒤 Spring 애플리케이션의 DB 설정을 수정했습니다.
프로젝트에 따라 설정 파일명은 db.properties, jdbc.properties, application.properties 등으로 다를 수 있습니다.
이번 시스템에서는 별도의 DB 설정 파일을 사용하고 있었습니다.
예시는 다음과 같습니다.
jdbc.DbType=mariadb
jdbc.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
jdbc.url=jdbc:log4jdbc:mariadb://127.0.0.1:3307/service_db
jdbc.username=service_user
jdbc.password=********
jdbc.validationQuery=SELECT 1
jdbc.minimumIdle=15
jdbc.maximumPoolSize=15
DB 종류
jdbc.DbType=mariadb
애플리케이션 내부에서 DB 종류에 따라 SQL이나 로직을 분기하는 경우가 있으므로 기존 코드에서 사용하는 값과 정확히 일치해야 합니다.
JDBC 드라이버
jdbc.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
해당 프로젝트는 log4jdbc를 사용하고 있었기 때문에 일반 MariaDB 드라이버가 아닌 DriverSpy를 사용했습니다.
log4jdbc를 사용하지 않는 프로젝트라면 다음 드라이버를 사용할 수 있습니다.
jdbc.driverClassName=org.mariadb.jdbc.Driver
JDBC URL
jdbc.url=jdbc:log4jdbc:mariadb://127.0.0.1:3307/service_db
JDBC URL에서는 다음 항목을 확인해야 합니다.
- JDBC 프로토콜
- DB 종류
- DB 서버 주소
- DB 포트
- 데이터베이스명
- log4jdbc 사용 여부
WAS와 DB가 같은 서버에 있으므로 127.0.0.1을 사용했습니다.
외부 IP를 사용하지 않아도 되기 때문에 네트워크를 외부로 우회하지 않고 내부에서 바로 접속할 수 있습니다.
Validation Query
jdbc.validationQuery=SELECT 1
MariaDB에서는 커넥션 유효성 확인 쿼리로 SELECT 1을 사용할 수 있습니다.
Oracle에서 사용하던 다음 형태를 그대로 남겨두는 경우도 있습니다.
SELECT 1 FROM DUAL
MariaDB에서도 동작할 수 있지만 MariaDB 환경에서는 SELECT 1만 사용해도 충분합니다.
Connection Pool
jdbc.minimumIdle=15
jdbc.maximumPoolSize=15
기존 운영 환경의 Connection Pool 설정을 우선 그대로 적용했습니다.
서버 이전과 동시에 Pool 설정까지 변경하면 장애 발생 시 원인을 구분하기 어려워질 수 있습니다.
신규 서버에서 서비스가 안정화된 뒤 서버 사양과 동시 접속량을 기준으로 별도로 조정하는 것이 좋습니다.
설정 파일을 수정한 뒤에는 Tomcat을 반드시 재기동해야 변경 내용이 반영됩니다.
11. Tomcat을 실행하고 로그를 확인했습니다
Tomcat 실행 스크립트를 이용해 서비스를 시작했습니다.
/home/service/server/bin/startup.sh
실행 직후에는 catalina.out 로그를 확인했습니다.
tail -f /home/service/server/logs/catalina.out
Tomcat 시작 로그에서는 다음 항목을 확인했습니다.
- Tomcat 시작 여부
- Spring Context 초기화 여부
- JDBC Driver 로딩 여부
- HikariCP Connection Pool 생성 여부
- DB 연결 여부
- Controller Mapping 여부
- 애플리케이션 예외 여부
정상적으로 시작된 경우에는 다음과 비슷한 내용을 확인할 수 있습니다.
Spring root WebApplicationContext initialized
HikariPool started
Mapped ...
Server startup in ...
DB 연결에 문제가 있다면 다음과 같은 오류가 발생할 수 있습니다.
Access denied for user
Connection refused
Unknown database
No suitable driver
Communications link failure
처음에는 신규 MariaDB 계정 인증과 JDBC 설정 문제로 DB 연결이 정상적으로 이루어지지 않았습니다.
MariaDB 사용자 Host와 권한을 다시 확인하고, JDBC URL의 주소와 포트, DB명을 수정한 뒤 정상적으로 연결되었습니다.
Connection Pool이 정상적으로 생성되고 초기 조회 쿼리가 실행되는 것을 로그에서 확인했습니다.
Tomcat 로그에 큰 오류가 없더라도 실제 포트가 LISTEN 상태인지 다시 확인했습니다.
ss -lntp | grep 8081
프로세스는 존재하지만 포트가 열리지 않은 경우에는 Tomcat 초기화 중 오류가 발생했을 가능성이 있습니다.
12. Nginx를 거치기 전에 Tomcat부터 단독 테스트했습니다
Tomcat이 정상적으로 실행된 뒤에는 Nginx나 도메인을 확인하기 전에 Tomcat에 직접 접속했습니다.
curl -I http://127.0.0.1:8081/
정상적인 경우 다음과 같은 응답을 받을 수 있습니다.
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Set-Cookie: JSESSIONID=...
이 단계에서 HTTP 200 응답이 확인된다면 다음 항목은 대부분 정상이라고 판단할 수 있습니다.
- Tomcat 실행
- WAR 배포
- Spring 초기화
- 기본 Controller 처리
- DB 연결
다만 첫 화면이 DB를 사용하지 않는 구조라면 DB를 사용하는 API도 별도로 확인해야 합니다.
curl http://127.0.0.1:8081/주요경로
Tomcat 단독 접속이 되지 않는 상태에서는 Nginx나 DNS 설정을 확인할 필요가 없습니다.
문제 구간을 다음과 같이 분리해서 확인하는 것이 중요합니다.
MariaDB
→ Tomcat
→ Nginx
→ DNS
각 구간을 순서대로 확인하면 문제 원인을 빠르게 찾을 수 있습니다.
13. 외부에서 Tomcat 포트에 접속되지 않았습니다
신규 서버 내부에서 다음 요청은 정상적으로 처리되었습니다.
curl http://127.0.0.1:8081/
하지만 외부 PC에서 신규 서버의 Tomcat 포트로 접속했을 때는 연결되지 않았습니다.
처음에는 Tomcat 설정 문제를 의심했지만 내부 요청이 정상적으로 처리되고 있었기 때문에 방화벽을 확인했습니다.
방화벽 실행 상태는 다음과 같이 확인했습니다.
firewall-cmd --state
현재 허용된 포트는 다음 명령으로 확인했습니다.
firewall-cmd --list-ports
확인 결과 신규 Tomcat 포트가 방화벽에 허용되어 있지 않았습니다.
테스트를 위해 포트를 추가했습니다.
firewall-cmd --add-port=8081/tcp
재부팅 후에도 유지되도록 영구 설정을 적용했습니다.
firewall-cmd --permanent --add-port=8081/tcp
firewall-cmd --reload
적용 결과를 다시 확인했습니다.
firewall-cmd --list-ports
방화벽 포트를 허용한 뒤 외부에서도 Tomcat으로 직접 접속할 수 있었습니다.
다만 실제 운영 환경에서는 Tomcat 포트를 외부에 계속 공개할 필요가 없습니다.
같은 서버의 Nginx가 Tomcat으로 Reverse Proxy하는 구조라면 외부에서는 80 또는 443 포트만 허용하고, Tomcat 포트는 내부에서만 접근하도록 구성하는 것이 더 안전합니다.
이번에는 신규 서버 동작을 확인하기 위한 테스트 목적으로 Tomcat 포트를 열었습니다.
서비스 전환이 완료된 뒤에는 불필요한 포트를 다시 차단하는 것이 좋습니다.
14. Nginx Reverse Proxy 설정을 추가했습니다
Tomcat 단독 동작을 확인한 뒤 Nginx 설정을 진행했습니다.
먼저 Nginx 상태를 확인했습니다.
systemctl status nginx
Nginx가 설치되어 있지 않다면 다음과 같이 설치할 수 있습니다.
yum install -y nginx
Nginx 서비스를 시작했습니다.
systemctl start nginx
서버 재부팅 후에도 자동으로 시작되도록 설정했습니다.
systemctl enable nginx
기존 서버의 Nginx 설정을 참고하여 신규 서버에도 대상 서비스의 server 블록을 추가했습니다.
server {
listen 80;
server_name service.example.com;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
proxy_set_header Host $http_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_pass http://127.0.0.1:8081;
이 값은 신규 서버에서 실제로 실행 중인 Tomcat 포트와 정확히 일치해야 합니다.
기존 서버의 Nginx 설정을 그대로 복사하는 경우 기존 Tomcat 포트가 남아 있을 수 있습니다.
proxy_pass 포트가 잘못되면 Nginx는 Tomcat에 연결하지 못하고 502 Bad Gateway를 반환할 수 있습니다.
Nginx 설정을 작성한 뒤 문법 검사를 진행했습니다.
nginx -t
정상적인 경우 다음과 같은 결과가 출력됩니다.
syntax is ok
test is successful
설정에 문제가 없다면 Nginx를 재시작하는 대신 reload로 반영했습니다.
systemctl reload nginx
Reload를 사용하면 기존 연결을 가능한 한 유지하면서 설정을 다시 읽을 수 있습니다.
15. DNS를 변경하기 전에 Nginx를 테스트했습니다
DNS가 아직 기존 서버를 가리키는 상태에서도 신규 서버의 Nginx 설정을 확인할 수 있습니다.
신규 서버 내부에서 Host 헤더를 직접 지정하여 테스트했습니다.
curl -I \
-H "Host: service.example.com" \
http://127.0.0.1/
정상적으로 설정되었다면 다음과 같이 HTTP 200 응답이 반환됩니다.
HTTP/1.1 200
Server: nginx
이 테스트를 통해 다음 흐름이 정상인지 확인할 수 있습니다.
Nginx
→ Tomcat
→ Spring 애플리케이션
→ MariaDB
외부 PC에서도 DNS를 변경하지 않고 신규 서버를 테스트할 수 있습니다.
curl -I \
--resolve service.example.com:80:신규서버IP \
http://service.example.com/
–resolve 옵션을 사용하면 실제 DNS 조회 결과와 관계없이 특정 도메인을 지정한 IP로 연결할 수 있습니다.
Host 헤더는 실제 도메인으로 유지되기 때문에 Nginx의 server_name설정까지 함께 테스트할 수 있습니다.
DNS 변경 전에 이 테스트가 정상적으로 완료되어야 서비스 전환 위험을 줄일 수 있습니다.
브라우저에서 테스트하려면 PC의 hosts 파일에 임시로 도메인과 신규 IP를 등록하는 방법도 있습니다.
하지만 테스트가 끝난 뒤 hosts 파일 설정을 제거하지 않으면 이후 DNS 변경 내용을 정상적으로 반영받지 못할 수 있으므로 주의해야 합니다.
16. DNS A 레코드를 신규 서버로 변경했습니다
신규 서버의 MariaDB, Tomcat, Nginx 구성이 모두 정상적으로 동작하는 것을 확인한 뒤 DNS를 변경했습니다.
기존에는 서비스 도메인의 A 레코드가 기존 서버 IP를 가리키고 있었습니다.
service.example.com
→ 기존 서버 IP
이를 신규 서버 IP로 변경했습니다.
service.example.com
→ 신규 서버 IP
DNS 관리 화면에는 여러 개의 레코드가 있을 수 있습니다.
@
www
service
admin
test
특정 서브도메인만 이전하는 경우에는 해당 서브도메인의 A 레코드만 변경해야 합니다.
비슷한 이름의 도메인을 여러 개 관리하는 경우에는 실제 서비스가 사용하는 상위 도메인의 DNS 설정 화면인지 반드시 확인해야 합니다.
DNS 레코드를 잘못된 도메인 관리 화면에서 수정하면 아무리 기다려도 실제 서비스에는 반영되지 않습니다.
DNS 변경 후 외부 DNS 서버에서 신규 IP가 조회되는지 확인했습니다.
dig @8.8.8.8 service.example.com
또는 다음과 같이 확인할 수 있습니다.
host service.example.com 8.8.8.8
Google Public DNS에서 신규 IP가 조회되면 외부 DNS에 변경 내용이 반영되기 시작한 것입니다.
다만 DNS 변경 직후 모든 사용자가 동시에 신규 IP를 조회하는 것은 아닙니다.
기존 DNS 응답의 TTL이 남아 있다면 일정 시간 동안 기존 IP가 반환될 수 있습니다.
17. DNS 변경 후 브라우저에서 502 Bad Gateway가 발생했습니다
DNS 변경 후 브라우저에서 서비스에 접속했지만 502 Bad Gateway가 나타났습니다.
처음에는 신규 서버의 Nginx 설정이나 Tomcat 연결에 문제가 있다고 생각했습니다.
하지만 앞서 신규 IP를 강제로 지정한 테스트에서는 정상적으로 HTTP 200이 반환되었습니다.
curl -I \
--resolve service.example.com:80:신규서버IP \
http://service.example.com/
결과는 정상적이었습니다.
HTTP/1.1 200
신규 서버 내부에서도 다음 요청이 정상적으로 처리되었습니다.
curl -I http://127.0.0.1:8081/
Host 헤더를 지정한 Nginx 요청도 정상적이었습니다.
curl -I \
-H "Host: service.example.com" \
http://127.0.0.1/
따라서 신규 서버의 다음 구성에는 문제가 없었습니다.
MariaDB 정상
Tomcat 정상
Spring 애플리케이션 정상
Nginx 정상
그렇다면 브라우저가 실제로 어느 IP에 접속하고 있는지 확인할 필요가 있었습니다.
Windows PC에서 다음 명령을 실행했습니다.
nslookup service.example.com
확인 결과 해당 PC에서는 여전히 기존 서버 IP가 조회되고 있었습니다.
외부 DNS는 이미 신규 서버 IP를 반환하고 있었지만, 로컬 PC 또는 사용 중인 DNS 서버에 기존 IP가 캐시되어 있었습니다.
즉, 브라우저는 신규 서버가 아니라 기존 서버에 계속 접속하고 있었습니다.
기존 서버의 설정 상태에 따라 그곳에서 502 오류가 반환되고 있었던 것입니다.
Windows DNS 캐시를 초기화했습니다.
ipconfig /flushdns
브라우저도 완전히 종료한 뒤 다시 실행했습니다.
이후 서비스 도메인에 접속하자 신규 서버에서 정상적으로 화면이 표시되었습니다.
모든 사용자에게 반드시 flushdns를 실행하도록 안내할 필요는 없습니다.
TTL이 만료되면 대부분의 클라이언트는 자동으로 신규 IP를 조회합니다.
다만 DNS 변경 직후 특정 PC에서만 계속 기존 서버에 접속하는 경우에는 로컬 DNS 캐시를 확인할 수 있습니다.
18. 신규 서버의 Access Log로 실제 접속을 확인했습니다
브라우저에서 화면이 정상적으로 열리는 것만으로 서버 이전이 끝났다고 판단하지 않았습니다.
신규 서버에 실제 요청이 들어오는지 Nginx Access Log를 확인했습니다.
tail -f /var/log/nginx/access.log
브라우저에서 로그인하고 메뉴를 이동하자 다음과 같은 요청이 계속 출력되었습니다.
GET / HTTP/1.1 200
POST /login HTTP/1.1 200
POST /api/example HTTP/1.1 200
GET /resources/example.css HTTP/1.1 200
GET /resources/example.js HTTP/1.1 200
다음 항목이 모두 정상적으로 처리되는지 확인했습니다.
- 첫 화면 조회
- 로그인
- 사용자 정보 조회
- 주요 메뉴
- DB 조회 API
- JavaScript
- CSS
- 이미지
- 폰트
- 세션 유지
첫 화면만 정상적으로 열리더라도 특정 API가 DB 연결 문제로 실패할 수 있습니다.
따라서 로그인 후 실제 주요 기능을 직접 사용해 보는 것이 중요합니다.
Access Log에서 실제 사용자 요청이 신규 서버에 들어오고 응답코드가 200으로 처리되는 것을 확인했습니다.
Tomcat 애플리케이션 로그도 함께 확인하여 서버 오류가 발생하지 않는지 점검했습니다.
19. 기존 서버를 바로 종료하지 않았습니다
DNS 변경 후 신규 서버가 정상적으로 동작했지만 기존 서버를 즉시 종료하지 않았습니다.
DNS는 TTL과 캐시의 영향을 받기 때문에 일부 사용자나 일부 DNS 서버에서는 일정 시간 동안 기존 IP가 반환될 수 있습니다.
기존 서버를 너무 빨리 종료하면 아직 이전 DNS 정보를 사용하는 사용자는 서비스에 접속하지 못할 수 있습니다.
따라서 다음과 같은 순서로 진행했습니다.
신규 서버 구성 완료
→ 신규 IP 강제 테스트
→ DNS 변경
→ 신규 서버 접속 로그 확인
→ 일정 시간 모니터링
→ 기존 서버 종료
다만 DB와 WAS가 함께 있는 서버를 이전할 때는 DNS 전환 중 데이터 정합성을 특히 주의해야 합니다.
기존 서버와 신규 서버의 DB가 각각 따로 존재하는 상태에서 양쪽 서비스가 동시에 동작하면 데이터가 분산될 수 있습니다.
예를 들어 DNS 캐시가 남아 있는 사용자는 기존 서버에 데이터를 저장하고, 신규 DNS를 조회한 사용자는 신규 서버에 데이터를 저장할 수 있습니다.
이 경우 두 DB의 데이터가 달라질 수 있습니다.
따라서 운영 DB를 이전할 때는 가능하면 점검 시간을 확보하고 다음과 같이 전환하는 것이 안전합니다.
1. 사용자에게 점검 시간 공지
2. 기존 서버의 Tomcat 또는 서비스 중지
3. 최종 DB 백업
4. 신규 서버 DB에 최종 데이터 복원
5. 신규 서버 서비스 시작
6. DNS 변경
7. 주요 기능 확인
8. 접속 로그 모니터링
점검 없이 서비스를 이전해야 한다면 DB 복제나 동기화 구조를 별도로 구성해야 합니다.
이번 작업에서는 최종 데이터 시점을 확인하고 서비스 전환 후 데이터 누락이 없는지 함께 점검했습니다.
20. 작업 중 발생한 주요 문제를 정리했습니다
이번 서버 이전 과정에서는 한 가지 문제가 아니라 여러 문제가 순서대로 발생했습니다.
MariaDB 사용자 인증 문제
신규 서버에 DB와 사용자 계정을 생성했지만 애플리케이션에서 접속하지 못했습니다.
다음 항목을 다시 확인했습니다.
DB 사용자명
DB 비밀번호
사용자 Host
DB 권한
DB 포트
DB명
JDBC URL
MariaDB CLI에서 직접 접속을 테스트하고 SHOW GRANTS로 권한을 확인한 뒤 해결했습니다.
JDBC 설정 불일치
기존 서버의 DB 정보가 애플리케이션 설정 파일에 남아 있었습니다.
다음 항목을 신규 서버 기준으로 수정했습니다.
jdbc.url
jdbc.username
jdbc.password
jdbc.driverClassName
jdbc.validationQuery
설정 파일 수정 후 Tomcat을 재기동하여 반영했습니다.
Tomcat은 실행되지만 외부에서 접속되지 않는 문제
Tomcat 프로세스와 내부 curl 요청은 정상이었지만 외부 PC에서는 접속되지 않았습니다.
원인은 방화벽에 Tomcat 포트가 허용되어 있지 않았기 때문이었습니다.
내부 접속은 성공하지만 외부 접속만 실패하는 경우에는 방화벽과 네트워크 정책을 확인해야 합니다.
Nginx Reverse Proxy 설정 문제
신규 서버에 서비스 도메인을 처리할 Nginx server 블록이 필요했습니다.
또한 proxy_pass가 신규 Tomcat의 실제 포트를 가리키도록 수정해야 했습니다.
Nginx 설정은 다음 명령으로 검증했습니다.
nginx -t
DNS 관리 대상 확인 문제
비슷한 이름의 여러 도메인과 서브도메인이 존재하면 잘못된 DNS 영역을 확인할 수 있습니다.
실제 서비스 도메인의 A 레코드가 어느 DNS 관리 영역에 등록되어 있는지 정확히 확인해야 합니다.
DNS 캐시로 인한 502 Bad Gateway
신규 서버는 정상적으로 동작하고 있었지만 일부 클라이언트가 기존 서버 IP를 계속 사용하고 있었습니다.
다음 명령을 비교하여 원인을 확인했습니다.
dig @8.8.8.8 service.example.com
nslookup service.example.com
외부 DNS는 신규 IP를 반환했지만 로컬 PC에서는 기존 IP가 조회되었습니다.
Windows DNS 캐시를 초기화한 뒤 정상적으로 신규 서버에 접속할 수 있었습니다.
21. 서버 이전 시 사용한 주요 명령어
Java 프로세스 확인
ps -ef | grep java
포트 확인
ss -lntp
Tomcat 포트 확인
ss -lntp | grep java
MariaDB 상태 확인
systemctl status mariadb
MariaDB 포트 확인
ss -lntp | grep mysqld
DB 직접 접속
mysql -h 127.0.0.1 -P 3307 -u service_user -p service_db
Tomcat 로그 확인
tail -f /home/service/server/logs/catalina.out
Tomcat 단독 접속 확인
curl -I http://127.0.0.1:8081/
방화벽 포트 확인
firewall-cmd --list-ports
Nginx 설정 검사
nginx -t
Nginx 설정 반영
systemctl reload nginx
Host 헤더를 이용한 Nginx 테스트
curl -I \
-H "Host: service.example.com" \
http://127.0.0.1/
신규 서버 IP 강제 테스트
curl -I \
--resolve service.example.com:80:신규서버IP \
http://service.example.com/
외부 DNS 확인
dig @8.8.8.8 service.example.com
host service.example.com 8.8.8.8
Windows DNS 조회
nslookup service.example.com
Windows DNS 캐시 초기화
ipconfig /flushdns
Nginx Access Log 확인
tail -f /var/log/nginx/access.log
Nginx Error Log 확인
tail -f /var/log/nginx/error.log
22. 운영 서버 이전 체크리스트
이번 작업을 기준으로 서버 이전 체크리스트를 정리했습니다.
이전 작업 전 확인
- 기존 서버 Java 버전을 확인합니다.
- Tomcat 버전을 확인합니다.
- Tomcat 실행 경로를 확인합니다.
- Tomcat HTTP 포트를 확인합니다.
- MariaDB 버전을 확인합니다.
- MariaDB 포트를 확인합니다.
- DB 용량을 확인합니다.
- DB 사용자와 Host 권한을 확인합니다.
- Nginx 설정 파일을 백업합니다.
- 애플리케이션 설정 파일을 백업합니다.
- DNS A 레코드와 TTL을 확인합니다.
- 방화벽 허용 포트를 확인합니다.
- 로그 경로를 확인합니다.
- 최종 서비스 전환 시간을 정합니다.
신규 서버 구성
- 기존 서버와 동일한 Java 버전을 설치합니다.
- MariaDB를 설치합니다.
- MariaDB 포트를 설정합니다.
- 데이터베이스를 생성합니다.
- 애플리케이션 사용자를 생성합니다.
- DB 권한을 부여합니다.
- 기존 DB 백업 파일을 복원합니다.
- 주요 테이블 건수를 비교합니다.
- View, Trigger, Procedure를 확인합니다.
- Tomcat을 설치하거나 이전합니다.
- WAR 파일을 배포합니다.
- 파일 소유권과 실행 권한을 확인합니다.
- server.xml포트를 확인합니다.
- DB 설정 파일을 수정합니다.
- JDBC Driver를 확인합니다.
- Tomcat을 실행합니다.
- Connection Pool 생성 로그를 확인합니다.
- Tomcat 단독 접속을 확인합니다.
Nginx 및 네트워크 구성
- Nginx를 설치하고 실행합니다.
- 대상 도메인의 server 블록을 작성합니다.
- proxy_pass 포트를 확인합니다.
- nginx -t 를 실행합니다.
- Nginx 설정을 reload합니다.
- Host 헤더 기반 내부 테스트를 진행합니다.
- 신규 IP 강제 접속 테스트를 진행합니다.
- 필요한 방화벽 포트를 확인합니다.
- 테스트 후 불필요한 Tomcat 외부 포트는 차단합니다.
서비스 전환
- 기존 서비스 중지 여부를 결정합니다.
- 최종 DB 백업을 진행합니다.
- 신규 서버에 최종 DB를 복원합니다.
- 신규 서버 서비스를 시작합니다.
- DNS A 레코드를 변경합니다.
- 외부 DNS에서 신규 IP를 확인합니다.
- 신규 서버 Access Log를 확인합니다.
- 로그인 기능을 확인합니다.
- 주요 조회 및 저장 기능을 확인합니다.
- 정적 리소스를 확인합니다.
- 세션 동작을 확인합니다.
- 오류 로그를 확인합니다.
- 일정 시간 모니터링합니다.
- 기존 서버 종료 시점을 결정합니다.
마무리
이번 서버 이전은 단순히 WAR 파일과 DB 백업 파일을 신규 서버에 복사하는 작업이 아니었습니다.
기존 서버 한 대에서 함께 운영되던 다음 구성요소를 신규 서버에 다시 구성해야 했습니다.
MariaDB
Tomcat
Spring 애플리케이션
Nginx
DNS
작업 과정에서는 MariaDB 사용자 인증 문제, JDBC 설정 변경, Tomcat 포트, 방화벽, Nginx Reverse Proxy, DNS 전파, DNS 캐시 등 여러 문제를 순서대로 해결했습니다.
가장 도움이 되었던 방법은 전체 구조를 한 번에 확인하지 않고 각 구간을 분리해서 테스트하는 것이었습니다.
1. MariaDB에 직접 접속합니다.
2. Tomcat에서 MariaDB 연결을 확인합니다.
3. Tomcat 단독 HTTP 요청을 확인합니다.
4. Nginx에서 Tomcat으로 요청이 전달되는지 확인합니다.
5. 신규 IP를 강제로 지정하여 도메인 접속을 확인합니다.
6. DNS를 변경합니다.
7. 신규 서버의 실제 접속 로그를 확인합니다.
브라우저에서 화면이 열리지 않는다고 해서 처음부터 DB, Tomcat, Nginx, DNS를 모두 의심하면 문제 범위가 너무 넓어집니다.
반대로 각 구간을 순서대로 확인하면 문제가 발생한 지점을 빠르게 찾을 수 있습니다.
운영 서버 이전에서는 화면이 정상적으로 열리는 것만 확인해서는 안 됩니다.
다음 항목도 함께 확인해야 합니다.
- 데이터 누락 여부
- DB 권한
- 로그인
- 주요 조회 기능
- 데이터 저장 기능
- 주요 API
- 정적 리소스
- 세션
- 접속 로그
- 오류 로그
- DNS 전파
- 실제 접속 서버
또한 DB와 WAS가 같은 서버에 있는 구조라면 최종 DB 백업 이후 기존 서버에서 발생한 데이터가 신규 서버에 누락되지 않도록 주의해야 합니다.
가능하면 짧은 점검 시간을 확보하여 기존 서비스를 중지하고 최종 DB를 이전한 뒤 DNS를 전환하는 것이 안전합니다.
이번 작업을 통해 서버 이전은 단순한 파일 복사가 아니라, 기존 운영 환경의 전체 흐름을 신규 서버에서 다시 구성하고 검증하는 작업이라는 점을 확인했습니다.