1편에서 티스토리를 떠나 서버를 직접 빌린 이유를 적었습니다. 이번 편은 그 다음 이야기입니다. 결제만 끝난 텅 빈 우분투 한 대에 SSH로 들어가서, 웹서버·데이터베이스·PHP를 손으로 올리고, 브라우저에서 PHP가 도는 걸 확인하는 데까지. 관리형 호스팅도, 원클릭 설치 이미지도 쓰지 않았습니다. 명령어는 전부 실제로 제 서버(Vultr 서울, Ubuntu 24.04, 2GB)에서 친 것들이고, 중간에 걸려 넘어졌던 지점도 그대로 적습니다. 익숙하면 40분, 처음이면 두 시간쯤 봅니다.
LEMP가 뭐길래, 왜 LAMP가 아닌가
LEMP는 Linux + Engine-X(Nginx, “엔진엑스”) + MySQL + PHP의 앞글자입니다. 흔히 듣는 LAMP에서 Apache 자리에 Nginx가 들어간 것뿐입니다. 웹 호스팅 업계 기본값은 오랫동안 LAMP였는데, 저는 Nginx를 골랐습니다. 이유는 하나, 메모리입니다.
제 서버는 램이 2GB입니다. 여기서 워드프레스 하나를 돌리려면 Nginx + MySQL + PHP-FPM 세 놈이 램을 나눠 씁니다. 그런데 MySQL 8이 혼자서 300~400MB를 가져갑니다. 남는 게 얼마 없어요. 이 상황에서 Apache와 Nginx의 설계 차이가 그대로 돈으로 환산됩니다.
| 비교 항목 | Apache (prefork) | Nginx |
|---|---|---|
| 동시 접속 처리 | 접속 1건당 프로세스/스레드 1개 | 적은 수의 워커가 이벤트 방식으로 다수 처리 |
| 접속이 몰릴 때 | 프로세스가 늘며 램 사용량이 같이 증가 | 램 사용량이 비교적 평평하게 유지 |
| 정적 파일(이미지·CSS) | 보통 | 빠름 — 블로그는 대부분이 정적 파일 |
| .htaccess | 폴더별로 사용 가능 | 없음 — 설정은 전부 서버 블록에서 |
| 설정 난이도 | 플러그인·튜토리얼이 압도적으로 많음 | 문법은 단순하나 직접 써야 함 |
| PHP 연결 방식 | 모듈로 내장 가능 | PHP-FPM에 넘김 (별도 구성 필수) |
정리하면 램이 넉넉하고 .htaccess에 의존하는 워드프레스 플러그인을 많이 쓸 거면 Apache, 램이 빠듯하고 설정 파일을 직접 만질 생각이면 Nginx입니다. 저는 후자였습니다. 대신 대가가 있습니다. Nginx는 .htaccess가 없어서 워드프레스 고유주소(퍼머링크)를 서버 블록에 직접 써줘야 하고, 이걸 빼먹으면 글 목록은 뜨는데 개별 글이 전부 404가 납니다. 뒤에서 다룹니다.
참고 — “그럼 Apache가 나쁜 건가?”는 아닙니다. 트래픽 적은 개인 블로그면 둘 다 잘 돕니다. 2GB라는 제 조건에서 Nginx가 유리했을 뿐입니다. 램 4GB 이상이면 익숙한 쪽을 쓰셔도 됩니다.
시작하기 전 — 서버 사양과 전제 조건

제가 쓰는 서버는 이렇습니다. 1편에서 인스턴스를 만들고 SSH 공개키를 등록하는 데까지는 끝났다고 보고 시작합니다.
| 항목 | 값 | 메모 |
|---|---|---|
| 업체 / 리전 | Vultr · 서울 | 한국에서 응답이 빠릅니다 |
| OS | Ubuntu 24.04 LTS | 2029년까지 표준 보안 업데이트 |
| 램 / 디스크 | 2GB / 64GB NVMe | 워드프레스 1개엔 충분 |
| 요금 | 월 12달러 수준 | 플랜·환율에 따라 달라집니다 |
| SSH 인증 | ed25519 키 | 비밀번호 로그인 아님 |
| 웹서버 | Nginx | 우분투 저장소 기본판 |
| DB | MySQL 8 | 우분투 저장소 기본판 |
| PHP | PHP 8.3 (FPM) | 24.04 기본 제공 — 뒤에서 자세히 |
로컬 PC(저는 윈도우 PowerShell)에서 접속합니다. 아래 서버IP 자리에 Vultr 대시보드에 뜬 주소를 넣으세요.
ssh root@서버IP
처음 접속하면 지문(fingerprint)을 신뢰하겠냐고 묻습니다. yes. 프롬프트가 root@vultr:~# 로 바뀌면 들어온 겁니다.
1단계 — 서버 기본 세팅 (5분)
웹서버를 올리기 전에 해둬야 손해가 없는 것들입니다. 특히 일반 사용자 만들기와 방화벽은 나중에 하려면 귀찮아집니다.
먼저 패키지 목록 갱신과 업그레이드:
apt update && apt upgrade -y
시간대를 서울로. 이걸 안 하면 워드프레스 예약 발행 시간과 서버 로그 시각이 UTC로 찍혀서 나중에 로그 볼 때 머리가 아픕니다.
timedatectl set-timezone Asia/Seoul
timedatectl
root로 계속 사는 건 좋지 않습니다. 일반 사용자를 만들고 sudo 권한을 줍니다. (mj 자리에 원하는 이름)
adduser mj
usermod -aG sudo mj
rsync --archive --chown=mj:mj ~/.ssh /home/mj
마지막 줄이 핵심입니다. root에 등록해둔 SSH 공개키를 새 계정으로 복사하는 것으로, 이걸 빼면 mj 계정으로 로그인이 안 됩니다. 지금 접속창은 그대로 두고 새 창을 열어 ssh mj@서버IP가 되는지 확인하세요. 되면 그때 root 창을 닫습니다.
주의 — 방화벽을 켜기 전에 SSH부터 허용해야 합니다. 순서를 바꾸면 나 자신이 서버에서 쫓겨나고, 그때부터는 Vultr 웹콘솔로 들어가야 합니다. 저는 예전에 이걸 해봤습니다.
방화벽 설정. 여기부터는 mj 계정입니다.
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
마지막으로 스왑. 2GB 램에서 MySQL과 PHP가 동시에 치솟을 때 스왑이 없으면 커널이 프로세스를 그냥 죽여버립니다(OOM Killer). 보통 MySQL이 먼저 죽고, 사이트는 DB 연결 오류를 뱉습니다. 스왑 2GB를 잡아둡니다.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
free -h의 Swap 줄에 2.0Gi가 보이면 됩니다. /etc/fstab에 넣는 줄을 빼먹으면 재부팅 후 스왑이 사라지니 꼭 넣으세요.
2단계 — Nginx 설치
한 줄입니다.
sudo apt install nginx -y
설치하면 자동으로 실행되고 부팅 시 자동 시작까지 등록됩니다. 확인:
systemctl status nginx
nginx -v
active (running)이 초록색으로 뜨면 성공입니다. Ubuntu 24.04는 저장소에 Nginx 1.24 계열이 들어 있습니다. 최신 기능이 급하지 않다면 저장소판이 보안 업데이트를 apt로 같이 받아서 편합니다.
이제 방화벽에서 웹 포트를 엽니다. Nginx는 설치 시 UFW 프로필을 등록해 두므로 포트 번호를 외울 필요가 없습니다.
sudo ufw allow 'Nginx Full'
sudo ufw status
| UFW 프로필 | 여는 포트 | 언제 쓰나 |
|---|---|---|
| Nginx HTTP | 80 | 아직 SSL 없을 때 |
| Nginx HTTPS | 443 | SSL만 쓸 때 |
| Nginx Full | 80 + 443 | 대부분 이것 — SSL 붙일 때 다시 안 만져도 됨 |
브라우저에서 http://서버IP로 들어가 보세요. Welcome to nginx! 페이지가 뜨면 바깥에서 내 서버가 보인다는 뜻입니다. 여기까지가 절반입니다.
3단계 — MySQL 설치와 보안 설정
sudo apt install mysql-server -y
systemctl status mysql
설치가 끝나면 보안 스크립트를 돌립니다. 기본 상태의 MySQL은 익명 계정과 test 데이터베이스가 열려 있습니다.
sudo mysql_secure_installation
질문이 순서대로 나옵니다. 제가 답한 대로 적으면:
| 질문 | 답 | 이유 |
|---|---|---|
| VALIDATE PASSWORD 컴포넌트를 쓸까? | Y → 강도 2(STRONG) | 약한 DB 비번은 나중에 반드시 후회합니다 |
| 익명 사용자를 지울까? | Y | 쓸 일 없습니다 |
| root의 원격 로그인을 막을까? | Y | DB는 같은 서버 안에서만 접속합니다 |
| test DB를 지울까? | Y | 누구나 접근 가능한 연습용 DB입니다 |
| 권한 테이블을 다시 읽을까? | Y | 변경사항 즉시 적용 |
참고 — 우분투의 MySQL 8은 root 계정이 비밀번호가 아니라 auth_socket 방식입니다. 즉 리눅스 root 권한이 있으면 비밀번호 없이 들어갑니다. 그래서 접속은 mysql -u root -p가 아니라 sudo mysql입니다. “비밀번호를 분명히 맞게 쳤는데 접속이 안 된다”는 대부분 이것 때문입니다.
워드프레스용 DB와 전용 계정을 만듭니다. root로 워드프레스를 붙이면 안 됩니다.
sudo mysql
CREATE DATABASE ddlabx DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'ddlabx_user'@'localhost' IDENTIFIED BY '여기에_강한_비밀번호';
GRANT ALL PRIVILEGES ON ddlabx.* TO 'ddlabx_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
utf8mb4가 중요합니다. 예전 utf8은 한글은 되지만 이모지가 깨집니다. 그리고 권한을 ddlabx.*로 좁힌 것도 의도적입니다. 이 계정은 이 DB 하나만 건드릴 수 있습니다.
4단계 — PHP 8.3 설치 (Ubuntu 24.04는 PPA가 필요 없다)
여기서 오래된 튜토리얼에 낚이기 쉽습니다. 인터넷 글 대부분이 ondrej/php PPA를 추가하라고 시킵니다. 22.04 시절 이야기입니다. Ubuntu 24.04는 기본 저장소에 PHP 8.3이 들어 있어서 PPA를 넣을 이유가 없습니다.
| 우분투 버전 | 기본 저장소 PHP | PPA 필요? |
|---|---|---|
| 20.04 LTS | PHP 7.4 | 사실상 필요 |
| 22.04 LTS | PHP 8.1 | 최신 원하면 필요 |
| 24.04 LTS | PHP 8.3 | 불필요 |
PPA를 굳이 안 쓰는 게 이득인 이유는 유지보수입니다. 외부 저장소를 붙이면 나중에 OS를 올릴 때 그 PPA가 새 버전을 지원하는지부터 확인해야 하고, 지원이 늦으면 업그레이드가 막힙니다. 기본 저장소만 쓰면 그런 게 없습니다.
sudo apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd \
php8.3-mbstring php8.3-xml php8.3-zip php8.3-intl php8.3-bcmath -y
php -v
각 확장이 왜 필요한지:
| 패키지 | 역할 | 빠지면 생기는 일 |
|---|---|---|
| php8.3-fpm | PHP 실행 엔진 (Nginx가 여기로 넘김) | PHP가 아예 안 돎 |
| php8.3-mysql | DB 연결 | “데이터베이스 연결 오류” |
| php8.3-gd | 이미지 리사이즈 | 썸네일 자동 생성 실패 |
| php8.3-curl | 외부 통신 | 플러그인 업데이트·API 호출 실패 |
| php8.3-mbstring | 멀티바이트 문자 처리 | 한글 글자수·자르기가 깨짐 |
| php8.3-xml | XML 파싱 | 사이트맵·RSS 문제 |
| php8.3-zip | 압축 해제 | 테마·플러그인 설치 실패 |
| php8.3-intl | 국제화 | 일부 플러그인(SEO 등)이 경고 |
다음 단계에서 쓸 소켓 경로를 지금 확인해 둡니다. 이 경로를 잘못 적는 게 502 오류의 1순위 원인입니다.
ls -l /run/php/
php8.3-fpm.sock이 보일 겁니다. 눈으로 확인한 이 이름을 그대로 복사하세요. 외워서 쓰지 마세요.
5단계 — Nginx 서버 블록에 PHP 물리기
마지막 조각입니다. Nginx는 PHP를 직접 실행하지 못합니다. “이건 PHP니까 네가 처리해”라고 PHP-FPM에 넘기는 규칙을 써줘야 합니다.
먼저 사이트 폴더:
sudo mkdir -p /var/www/ddlabx
sudo chown -R www-data:www-data /var/www/ddlabx
설정 파일을 새로 만듭니다.
sudo nano /etc/nginx/sites-available/ddlabx
내용:
server {
listen 80;
listen [::]:80;
server_name ddlabx.com www.ddlabx.com;
root /var/www/ddlabx;
index index.php index.html;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
이 파일에서 반드시 알고 넘어가야 할 네 줄이 있습니다.
| 줄 | 하는 일 | 빼먹으면 |
|---|---|---|
try_files $uri $uri/ /index.php?$args; | 없는 경로를 워드프레스에 넘김 | 홈은 되는데 개별 글이 전부 404 |
fastcgi_pass unix:/run/php/php8.3-fpm.sock; | PHP 처리 위임 | 502 Bad Gateway |
client_max_body_size 64M; | 업로드 크기 제한 | 이미지 올릴 때 413 오류 |
location ~ /\.ht { deny all; } | 설정 파일 노출 차단 | 보안 구멍 |
활성화하고 기본 사이트를 끕니다.
sudo ln -s /etc/nginx/sites-available/ddlabx /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/default
sudo nginx -t
nginx -t가 syntax is ok / test is successful를 뱉을 때만 다음으로 갑니다. 실패하면 오류 메시지에 파일명과 줄 번호가 정확히 찍힙니다. 십중팔구 세미콜론입니다.
sudo systemctl reload nginx
테스트 파일을 만들어 PHP가 실제로 도는지 봅니다.
echo "<?php phpinfo();" | sudo tee /var/www/ddlabx/info.php
http://서버IP/info.php에 접속해서 보라색 PHP 정보 표가 뜨면 LEMP 완성입니다. Nginx가 요청을 받아 PHP-FPM에 넘기고 결과를 돌려준 겁니다.
주의 — 확인했으면 즉시 지우세요. phpinfo는 PHP 버전, 설치 확장, 파일 경로까지 통째로 보여줍니다. 공격자에게 안내문을 붙여두는 것과 같습니다.
sudo rm /var/www/ddlabx/info.php
여기서 막힙니다 — 증상별 원인표
제가 겪었거나 겪을 뻔한 것들입니다. 오류 화면을 보고 당황하기 전에 이 표부터 보세요.
| 증상 | 가장 흔한 원인 | 확인 방법 |
|---|---|---|
| 502 Bad Gateway | fastcgi_pass 소켓 경로 오타 / PHP-FPM 죽음 | ls /run/php/ 로 실제 이름 확인, systemctl status php8.3-fpm |
| PHP 파일이 실행 대신 다운로드됨 | location ~ \.php$ 블록 누락 | 서버 블록 다시 확인 |
| 홈은 되는데 개별 글이 404 | try_files 줄 누락 | 서버 블록의 location / |
| 403 Forbidden | index 파일 없음 / 소유권이 www-data가 아님 | ls -l /var/www/ddlabx |
| 500 Internal Server Error | PHP 확장 누락, 메모리 부족, 코드 오류 | sudo tail -50 /var/log/nginx/error.log |
| 브라우저 연결 자체가 안 됨 | UFW에서 80 포트 안 열림 | sudo ufw status |
| 이미지 업로드 시 413 | client_max_body_size 기본값(1M) | 서버 블록 + php.ini 둘 다 확인 |
| nginx -t 실패 | 세미콜론·중괄호 누락 | 메시지에 찍힌 줄 번호 |
기억할 로그 위치는 세 개뿐입니다.
| 로그 | 경로 | 볼 때 |
|---|---|---|
| Nginx 오류 | /var/log/nginx/error.log | 502·500·403이 뜰 때 제일 먼저 |
| Nginx 접속 | /var/log/nginx/access.log | 요청이 서버까지 왔는지 확인 |
| PHP-FPM | /var/log/php8.3-fpm.log | PHP 워커가 죽을 때 |
실시간으로 보려면 sudo tail -f /var/log/nginx/error.log를 한쪽 창에 띄워두고 다른 창에서 새로고침하면 됩니다. 이게 서버 문제 해결의 8할입니다.
자주 묻는 질문
Q. 원클릭 설치 이미지 놔두고 왜 손으로 하나요?
A. Vultr에도 워드프레스 이미지가 있고, 그게 편한 건 맞습니다. 다만 그렇게 올리면 뭐가 어디 있는지 모른 채로 시작합니다. 나중에 502가 뜨면 손댈 곳을 못 찾습니다. 저는 한 번은 손으로 해보는 값이 하루치 삽질보다 싸다고 봤습니다. 실제로 이 과정을 거친 덕에 이후 오류들은 대부분 몇 분 안에 잡았습니다.
Q. MySQL 대신 MariaDB는 어떤가요?
A. 워드프레스는 둘 다 공식 지원하고, 설치 명령만 mariadb-server로 바뀌는 수준입니다. MariaDB가 기본 메모리 사용량이 대체로 더 가벼워서 램이 1GB라면 진지하게 고려할 만합니다. 저는 자료와 사례가 많은 쪽이 문제 해결에 유리하다고 판단해 MySQL로 갔습니다.
Q. 2GB로 워드프레스가 버티나요?
A. 개인 블로그 트래픽이면 넉넉합니다. 다만 스왑은 반드시 잡아두세요. 대략적인 상주 메모리는 MySQL이 300~400MB, PHP-FPM 워커가 개당 30~50MB, Nginx는 10MB 안팎입니다. 정확한 수치는 서버마다 다르니 free -h와 ps aux --sort=-%mem | head로 본인 서버를 직접 재보세요.
Q. 재부팅하면 다시 켜야 하나요?
A. 아닙니다. apt로 설치한 Nginx·MySQL·PHP-FPM은 부팅 시 자동 시작이 등록됩니다. systemctl is-enabled nginx mysql php8.3-fpm로 세 개 모두 enabled인지 확인해 두세요. 스왑만 /etc/fstab에 등록했는지 다시 보시면 됩니다.
Q. 아직 HTTPS가 아닌데요?
A. 맞습니다. 지금은 80 포트만 씁니다. Let’s Encrypt 무료 인증서로 HTTPS를 붙이는 건 다음 편에서 다룹니다. 그때 UFW를 다시 안 만져도 되는 이유가 앞에서 Nginx Full을 열어뒀기 때문입니다.
정리하면
- 2GB 서버에서는 램이 곧 성능입니다. Apache 대신 Nginx를 고른 유일한 이유가 이것입니다.
- Ubuntu 24.04는 PHP 8.3이 기본 저장소에 있습니다. PPA를 추가하라는 튜토리얼은 대부분 22.04 시절 글입니다.
- 순서를 지키세요 — 사용자 생성 → SSH 허용 → 방화벽 켜기. 뒤집으면 본인이 잠깁니다.
- 소켓 경로는
ls /run/php/로 눈으로 확인하고 복사하세요. 502의 1순위 원인입니다. try_files한 줄이 워드프레스 개별 글 404를 좌우합니다.- phpinfo로 확인했으면 즉시 삭제.
- 막히면
/var/log/nginx/error.log부터. 추측하지 말고 로그를 보세요.
여기까지가 빈 서버에 LEMP를 올리는 전 과정입니다. 다음 편에서는 이 상태에 워드프레스를 얹고, 제가 실제로 마주쳤던 500 오류를 어떻게 잡았는지 로그와 함께 적겠습니다.