Category: Uncategorized

  • 서브넷(Subnet)과 CIDR 표기법 이해하기

    서브넷(Subnet)과 CIDR 표기법 이해하기

    서버를 운영하다 보면 네트워크 설정에서 192.168.1.0/24 같은 표기를 자주 만나게 됩니다. AWS에서 VPC를 만들 때도 나오고, 방화벽 규칙을 설정할 때도 나오고, 서버 간 통신을 구성할 때도 나옵니다. 슬래시 뒤에 붙는 숫자가 뭘 의미하는지 모르면 네트워크 설정을 할 때마다 찜찜한 채로 넘어가게 됩니다. 이번 글에서는 IP 주소의 구조부터 시작해서 서브넷과 CIDR 표기법이 어떤 의미인지를 정리해보겠습니다.

    IP 주소의 구조부터 다시 보기

    서브넷을 이해하려면 IP 주소가 어떻게 구성되어 있는지를 먼저 알아야 합니다.

    IPv4 주소는 192.168.1.100처럼 점으로 구분된 네 개의 숫자로 이루어져 있습니다. 각 숫자는 0부터 255까지의 값을 가집니다. 사람이 보기에는 십진수 네 덩어리지만 컴퓨터 내부에서는 32비트 이진수로 처리됩니다. 192.168.1.100을 이진수로 바꾸면 11000000.10101000.00000001.01100100입니다.

    이 32비트는 두 부분으로 나뉩니다. 앞부분은 네트워크 주소이고 뒷부분은 호스트 주소입니다. 네트워크 주소는 “어떤 네트워크에 속하는가”를 나타내고, 호스트 주소는 “그 네트워크 안에서 어떤 장비인가”를 나타냅니다.

    현실 세계의 주소에 비유하면 네트워크 주소는 동네 이름이고 호스트 주소는 집 번호입니다. 같은 동네에 사는 사람들은 네트워크 주소가 같고 호스트 주소만 다릅니다. 다른 동네에 사는 사람에게 데이터를 보내려면 라우터를 거쳐야 합니다.

    문제는 32비트 중에서 어디까지가 네트워크 주소이고 어디부터가 호스트 주소인지를 알 수 없다는 점입니다. 이걸 구분해주는 게 서브넷 마스크입니다.

    서브넷 마스크란 무엇인가

    서브넷 마스크는 IP 주소에서 네트워크 부분과 호스트 부분의 경계를 알려주는 값입니다. 서브넷 마스크도 IP 주소처럼 32비트로 되어 있는데, 앞쪽에 1이 연속으로 나오고 뒤쪽에 0이 연속으로 나오는 구조입니다.

    예를 들어 서브넷 마스크가 255.255.255.0이면 이진수로는 11111111.11111111.11111111.00000000입니다. 1인 부분이 네트워크 주소이고 0인 부분이 호스트 주소입니다. 이 경우 앞의 24비트가 네트워크 주소이고 뒤의 8비트가 호스트 주소입니다.

    IP 주소가 192.168.1.100이고 서브넷 마스크가 255.255.255.0이면, 네트워크 주소는 192.168.1이고 호스트 주소는 100입니다. 같은 네트워크에 속한 장비들은 전부 192.168.1로 시작하고 마지막 숫자만 다릅니다. 192.168.1.1부터 192.168.1.254까지가 이 네트워크에서 사용할 수 있는 호스트 주소입니다.

    왜 254개인지 궁금할 수 있습니다. 호스트 부분이 8비트이면 0부터 255까지 총 256개의 값이 가능하지만, 호스트 부분이 전부 0인 주소(192.168.1.0)는 네트워크 자체를 가리키는 네트워크 주소로 예약되어 있고, 전부 1인 주소(192.168.1.255)는 브로드캐스트 주소로 예약되어 있습니다. 그래서 실제 장비에 할당할 수 있는 주소는 256에서 2를 뺀 254개입니다.

    CIDR 표기법이란

    CIDR은 Classless Inter-Domain Routing의 약자입니다. IP 주소 뒤에 슬래시와 숫자를 붙여서 서브넷 마스크를 간단하게 표현하는 방식입니다.

    192.168.1.0/24라고 쓰면 앞의 24비트가 네트워크 주소라는 뜻입니다. 이건 서브넷 마스크 255.255.255.0과 같은 의미입니다. 슬래시 뒤의 숫자가 서브넷 마스크에서 1이 몇 개 연속되는지를 나타냅니다.

    10.0.0.0/16이면 앞의 16비트가 네트워크 주소입니다. 서브넷 마스크로는 255.255.0.0입니다. 호스트 부분이 16비트이므로 사용 가능한 호스트 수는 2의 16승에서 2를 뺀 65,534개입니다.

    172.16.0.0/12면 앞의 12비트가 네트워크 주소이고 호스트 부분이 20비트입니다. 사용 가능한 호스트 수는 2의 20승에서 2를 뺀 1,048,574개입니다.

    숫자가 작을수록 네트워크 범위가 넓고 사용할 수 있는 호스트가 많습니다. 숫자가 클수록 네트워크 범위가 좁고 호스트 수가 적습니다. /32는 호스트 부분이 0비트이므로 단 하나의 IP 주소를 가리킵니다.

    자주 보는 CIDR 표기를 정리하면 이렇습니다. /8은 서브넷 마스크 255.0.0.0이고 약 1,677만 개의 호스트를 수용합니다. /16은 255.255.0.0이고 약 65,000개입니다. /24는 255.255.255.0이고 254개입니다. /32는 255.255.255.255이고 1개입니다.

    서브넷팅이란 무엇인가

    서브넷팅은 하나의 큰 네트워크를 여러 개의 작은 네트워크로 쪼개는 것입니다.

    예를 들어 회사에서 192.168.1.0/24 네트워크를 사용하고 있다고 합시다. 이 네트워크에는 254개의 장비를 연결할 수 있습니다. 그런데 부서가 세 개 있고 부서별로 네트워크를 분리하고 싶습니다. 보안상 다른 부서의 트래픽이 내 네트워크에 들어오지 않게 하고 싶을 수도 있고, 장애가 났을 때 영향 범위를 줄이고 싶을 수도 있습니다.

    이때 /24 네트워크를 더 작은 단위로 나눌 수 있습니다. 192.168.1.0/26으로 나누면 어떻게 될까요. /26은 네트워크 부분이 26비트이고 호스트 부분이 6비트입니다. 호스트 수는 2의 6승에서 2를 뺀 62개입니다. /24 하나가 /26 네 개로 쪼개집니다.

    192.168.1.0/26은 192.168.1.0부터 192.168.1.63까지의 범위입니다. 192.168.1.64/26은 192.168.1.64부터 192.168.1.127까지입니다. 192.168.1.128/26은 192.168.1.128부터 192.168.1.191까지이고, 192.168.1.192/26은 192.168.1.192부터 192.168.1.255까지입니다. 네 개의 서브넷이 만들어졌고 각각 62개의 호스트를 수용할 수 있습니다.

    각 서브넷의 첫 번째 주소는 네트워크 주소이고 마지막 주소는 브로드캐스트 주소이므로 실제 사용 가능한 범위는 첫 번째와 마지막을 제외한 나머지입니다. 192.168.1.0/26에서 장비에 할당할 수 있는 주소는 192.168.1.1부터 192.168.1.62까지입니다.

    클라우드 환경에서의 서브넷

    AWS, GCP 같은 클라우드를 사용할 때 서브넷 개념이 더 자주 나옵니다.

    AWS에서 서버를 만들려면 먼저 VPC(Virtual Private Cloud)를 만들어야 합니다. VPC는 클라우드 안에 격리된 내 전용 네트워크를 만드는 겁니다. VPC를 만들 때 IP 범위를 CIDR로 지정합니다. 예를 들어 10.0.0.0/16으로 VPC를 만들면 10.0.0.0부터 10.0.255.255까지의 IP를 사용할 수 있는 네트워크가 생깁니다.

    VPC 안에서 다시 서브넷을 만들어야 합니다. 서브넷마다 가용 영역(AZ)을 지정합니다. 10.0.1.0/24를 가용 영역 A에, 10.0.2.0/24를 가용 영역 B에 만드는 식입니다. 서버를 만들 때 어떤 서브넷에 배치할지 선택하면 해당 서브넷의 IP 범위에서 주소가 할당됩니다.

    퍼블릭 서브넷과 프라이빗 서브넷을 나누는 것도 흔한 구성입니다. 퍼블릭 서브넷에는 외부에서 접근 가능한 웹 서버를 두고, 프라이빗 서브넷에는 외부에서 직접 접근할 수 없는 데이터베이스 서버를 둡니다. 서브넷 자체에 퍼블릭이나 프라이빗 속성이 있는 건 아니고, 인터넷 게이트웨이와 연결된 라우팅 테이블을 가진 서브넷이 퍼블릭, 그렇지 않은 서브넷이 프라이빗입니다.

    이런 구성을 할 때 CIDR 표기를 이해하지 못하면 IP 범위가 겹치거나 호스트 수가 부족한 문제가 생깁니다. VPC를 /16으로 잡아두면 서브넷을 충분히 쪼갤 수 있는 여유가 생기고, 서브넷은 /24 정도로 만들면 서브넷 하나당 251개의 호스트를 사용할 수 있습니다. AWS는 각 서브넷에서 5개의 IP를 예약하기 때문에 /24 서브넷의 실제 사용 가능한 호스트 수는 256에서 5를 뺀 251개입니다.

    방화벽 설정에서의 CIDR

    방화벽이나 보안 그룹 규칙을 설정할 때도 CIDR을 사용합니다.

    AWS 보안 그룹에서 SSH 접속을 허용할 때 소스에 0.0.0.0/0을 넣으면 모든 IP에서 접속을 허용한다는 뜻입니다. /0은 네트워크 부분이 0비트이므로 모든 IP가 해당됩니다. 보안상 매우 위험하기 때문에 SSH는 내 IP만 허용하는 게 좋습니다.

    내 IP가 203.0.113.50이라면 소스에 203.0.113.50/32를 넣으면 됩니다. /32는 정확히 그 IP 하나만을 의미합니다.

    회사 네트워크 전체에서 접속을 허용하고 싶으면 회사 IP 대역을 CIDR로 넣습니다. 회사가 203.0.113.0부터 203.0.113.255까지의 IP를 사용한다면 203.0.113.0/24를 넣으면 됩니다.

    이전 글에서 다뤘던 UFW 방화벽에서도 마찬가지입니다.

    sudo ufw allow from 203.0.113.0/24 to any port 22

    이 명령어는 203.0.113.0/24 대역에서 오는 SSH 접속만 허용한다는 뜻입니다.

    사설 IP 대역과 CIDR

    인터넷에서 사용하는 공인 IP와 별개로, 내부 네트워크에서만 사용하는 사설 IP 대역이 정해져 있습니다. 이 대역은 CIDR로 표현하면 세 가지입니다.

    10.0.0.0/8은 10.0.0.0부터 10.255.255.255까지의 범위입니다. 가장 넓은 사설 IP 대역이고 대규모 네트워크에서 주로 사용합니다. AWS VPC의 기본 추천 대역이기도 합니다.

    172.16.0.0/12는 172.16.0.0부터 172.31.255.255까지의 범위입니다. 중간 규모의 네트워크에서 사용합니다.

    192.168.0.0/16은 192.168.0.0부터 192.168.255.255까지의 범위입니다. 가정이나 소규모 사무실에서 가장 흔하게 쓰이는 대역입니다. 집에서 공유기에 연결하면 보통 192.168.0.x나 192.168.1.x 주소를 받는 게 이 대역에 해당합니다.

    VPC나 내부 네트워크를 구성할 때는 반드시 이 사설 IP 대역 안에서 CIDR을 지정해야 합니다. 공인 IP 대역을 사설 네트워크에 사용하면 해당 공인 IP로의 통신이 안 되는 문제가 생깁니다.

    CIDR 계산이 헷갈릴 때

    서브넷 계산이 익숙하지 않으면 직접 계산하는 것보다 도구를 사용하는 게 빠릅니다. 터미널에서 ipcalc라는 도구를 설치하면 CIDR에 대한 정보를 바로 확인할 수 있습니다.

    sudo apt install ipcalc
    ipcalc 192.168.1.0/24

    실행하면 네트워크 주소, 브로드캐스트 주소, 사용 가능한 호스트 범위, 호스트 수를 전부 보여줍니다. 웹에서 “CIDR calculator”로 검색해도 온라인 계산기가 많이 나옵니다. 서브넷 설계를 할 때 이런 도구로 먼저 확인하고 설정하면 실수를 줄일 수 있습니다.

    마무리

    서브넷은 하나의 네트워크를 용도에 맞게 작은 네트워크로 나누는 개념이고, CIDR은 네트워크 범위를 슬래시와 숫자로 간결하게 표현하는 방법입니다. 슬래시 뒤의 숫자가 클수록 네트워크가 좁고 호스트 수가 적고, 숫자가 작을수록 네트워크가 넓고 호스트 수가 많습니다. 클라우드 환경에서 VPC와 서브넷을 만들 때, 방화벽 규칙에서 IP 범위를 지정할 때 이 개념이 바로 적용됩니다. 처음에는 이진수 계산이 복잡하게 느껴지지만 자주 쓰는 값들만 기억해두면 실무에서 크게 어려울 일은 없습니다. 다음 글에서는 DNS 레코드의 종류와 각각의 역할에 대해 자세히 다뤄보겠습니다.

  • SSL/TLS 동작 원리 쉽게 이해하기

    SSL/TLS 동작 원리 쉽게 이해하기

    인터넷에서 로그인을 하거나 카드 번호를 입력할 때, 그 정보가 내 컴퓨터에서 서버까지 가는 동안 누군가 몰래 들여다보고 있으면 큰일입니다. 실제로 암호화가 없던 시절에는 같은 와이파이를 쓰는 사람이 옆에서 패킷을 가로채면 비밀번호가 그대로 보였습니다. 이런 문제를 해결하기 위해 만들어진 게 SSL이고, 그 후속 버전이 TLS입니다. 이전 글에서 Let’s Encrypt로 인증서를 적용하는 방법을 다뤘는데, 이번 글에서는 그 인증서가 실제로 어떤 원리로 통신을 보호하는지를 정리해보겠습니다.

    SSL과 TLS는 같은 건가 다른 건가

    SSL(Secure Sockets Layer)은 1990년대에 넷스케이프에서 만든 암호화 프로토콜입니다. SSL 3.0까지 나왔고, 이후에 표준화 과정을 거치면서 이름이 TLS(Transport Layer Security)로 바뀌었습니다. TLS 1.0이 SSL 3.0의 후속 버전이고, 현재는 TLS 1.3까지 나와 있습니다.

    엄밀히 말하면 SSL과 TLS는 다른 프로토콜입니다. SSL은 보안 취약점이 발견되어서 지금은 사용하지 않습니다. 현재 HTTPS에서 실제로 사용되는 건 전부 TLS입니다. 그런데 SSL이라는 이름이 워낙 오래 쓰여서 아직도 SSL 인증서, SSL 적용이라는 표현을 관행적으로 사용합니다. Let’s Encrypt에서 발급받는 것도 실제로는 TLS 인증서이지만 다들 SSL 인증서라고 부릅니다. 이 글에서도 편의상 SSL/TLS를 혼용하겠습니다.

    암호화의 기본 개념 — 대칭키와 비대칭키

    TLS의 동작 원리를 이해하려면 먼저 두 가지 암호화 방식을 알아야 합니다.

    대칭키 암호화는 하나의 키로 암호화하고 같은 키로 복호화하는 방식입니다. 자물쇠와 열쇠가 하나인 것과 같습니다. 내가 데이터를 암호화해서 보내면 상대방도 같은 키를 가지고 있어야 복호화할 수 있습니다. 속도가 빠르다는 장점이 있지만, 문제는 그 키를 상대방에게 어떻게 전달하느냐입니다. 키를 전달하는 과정에서 누군가 가로채면 암호화가 무의미해집니다.

    비대칭키 암호화는 키가 두 개입니다. 공개키와 개인키라고 부릅니다. 공개키로 암호화한 데이터는 개인키로만 복호화할 수 있고, 개인키로 암호화한 데이터는 공개키로만 복호화할 수 있습니다. 공개키는 말 그대로 누구에게든 공개해도 됩니다. 누군가 내 공개키로 데이터를 암호화해서 보내면 내 개인키를 가진 나만 복호화할 수 있으니까요. 키 전달 문제가 해결되지만 대칭키에 비해 속도가 느리다는 단점이 있습니다.

    TLS는 이 두 가지를 조합해서 사용합니다. 비대칭키로 안전하게 대칭키를 교환하고, 이후 실제 데이터 전송은 빠른 대칭키로 암호화합니다. 두 방식의 장점만 취하는 구조입니다.

    인증서는 무엇이고 왜 필요한가

    비대칭키 암호화에서 공개키를 받았다고 해봅시다. 그런데 이 공개키가 정말 내가 접속하려는 서버의 것인지 어떻게 확신할 수 있을까요. 중간에 공격자가 자기 공개키를 대신 보내놓고 통신을 가로챌 수도 있습니다. 이걸 중간자 공격(Man-in-the-Middle Attack)이라고 합니다.

    이 문제를 해결하기 위해 인증서가 존재합니다. 인증서는 “이 공개키는 이 도메인의 것이 맞다”고 신뢰할 수 있는 제3자가 보증하는 문서입니다. 이 제3자를 인증 기관(CA, Certificate Authority)이라고 합니다. Let’s Encrypt가 바로 CA 중 하나입니다.

    인증서 안에는 여러 정보가 들어 있습니다. 도메인 이름, 서버의 공개키, 인증서를 발급한 CA 정보, 유효 기간, 그리고 CA의 디지털 서명이 포함됩니다.

    디지털 서명은 CA가 자신의 개인키로 인증서 내용을 암호화한 것입니다. 브라우저는 CA의 공개키로 이 서명을 복호화해서 인증서 내용이 변조되지 않았는지 확인합니다. 브라우저에는 신뢰할 수 있는 CA들의 공개키가 미리 내장되어 있기 때문에 별도의 과정 없이 검증이 가능합니다.

    TLS 핸드셰이크 과정

    브라우저에서 https로 시작하는 주소에 접속하면 실제 데이터를 주고받기 전에 TLS 핸드셰이크라는 과정을 거칩니다. 이 과정에서 서버의 신원을 확인하고 암호화에 사용할 대칭키를 교환합니다.

    TLS 1.2 기준으로 핸드셰이크 과정을 순서대로 설명하겠습니다.

    첫 번째 단계는 Client Hello입니다. 브라우저가 서버에 접속 요청을 보내면서 자신이 지원하는 TLS 버전, 사용 가능한 암호화 방식 목록, 그리고 랜덤한 데이터를 함께 보냅니다. 이 랜덤 데이터는 나중에 대칭키를 만드는 데 사용됩니다.

    두 번째 단계는 Server Hello입니다. 서버가 클라이언트가 보낸 목록 중에서 사용할 TLS 버전과 암호화 방식을 선택하고, 서버 쪽 랜덤 데이터와 함께 응답합니다. 이때 서버의 인증서도 함께 보냅니다.

    세 번째 단계는 인증서 검증입니다. 브라우저가 서버로부터 받은 인증서를 확인합니다. 인증서가 신뢰할 수 있는 CA에서 발급한 것인지, 유효 기간이 지나지 않았는지, 인증서에 적힌 도메인이 접속하려는 도메인과 일치하는지를 확인합니다. 검증에 실패하면 브라우저가 경고 화면을 보여줍니다. 주소창에 “이 연결은 안전하지 않습니다”라고 뜨는 게 이 단계에서 검증이 실패한 경우입니다.

    네 번째 단계는 키 교환입니다. 브라우저가 Pre-Master Secret이라는 랜덤 데이터를 생성하고, 서버의 공개키로 암호화해서 보냅니다. 서버만 자신의 개인키로 이걸 복호화할 수 있기 때문에 중간에 누가 가로채도 내용을 알 수 없습니다.

    다섯 번째 단계는 대칭키 생성입니다. 클라이언트와 서버 양쪽이 Client Hello에서 보낸 랜덤 데이터, Server Hello에서 보낸 랜덤 데이터, 그리고 Pre-Master Secret 세 가지를 조합해서 동일한 대칭키(Session Key)를 만듭니다. 이 대칭키는 네트워크를 통해 직접 전달된 적이 없기 때문에 안전합니다.

    여섯 번째 단계부터 실제 데이터 전송이 시작됩니다. 양쪽이 동일한 대칭키를 갖게 되었으니 이후의 모든 통신은 이 대칭키로 암호화됩니다. 비대칭키 암호화는 핸드셰이크 과정에서만 사용되고, 실제 데이터 전송은 속도가 빠른 대칭키 암호화로 처리됩니다.

    TLS 1.3에서 달라진 점

    TLS 1.2의 핸드셰이크는 클라이언트와 서버 사이에 두 번의 왕복(2-RTT)이 필요합니다. TLS 1.3에서는 이걸 한 번의 왕복(1-RTT)으로 줄였습니다. 클라이언트가 처음 요청을 보낼 때 키 교환에 필요한 정보를 미리 포함시키기 때문입니다. 핸드셰이크가 빨라졌다는 건 사이트가 처음 로딩되는 속도도 빨라졌다는 뜻입니다.

    보안도 강화되었습니다. TLS 1.2에서는 다양한 암호화 방식을 지원했는데 그중 일부에 취약점이 발견되었습니다. TLS 1.3에서는 안전하지 않은 암호화 방식을 전부 제거하고 검증된 방식만 남겼습니다. 선택지가 줄었지만 그만큼 잘못된 설정으로 보안이 약해질 가능성도 줄었습니다.

    이전에 같은 서버에 접속한 적이 있다면 0-RTT 연결도 가능합니다. 이전 세션에서 저장해둔 정보를 사용해서 핸드셰이크 없이 바로 데이터를 보낼 수 있습니다. 다만 0-RTT는 재전송 공격(Replay Attack)에 취약할 수 있어서 모든 상황에서 권장되는 건 아닙니다.

    인증서 체인이란

    인증서 검증 과정에서 한 가지 더 알아둘 게 있습니다. 실제로 서버의 인증서를 직접 발급하는 건 루트 CA가 아니라 중간 CA인 경우가 대부분입니다.

    구조를 보면 이렇습니다. 루트 CA가 중간 CA에게 인증서를 발급하고, 중간 CA가 서버에게 인증서를 발급합니다. 서버가 브라우저에 인증서를 보낼 때는 서버 인증서와 중간 CA 인증서를 함께 보냅니다. 브라우저는 서버 인증서를 중간 CA의 공개키로 검증하고, 중간 CA 인증서를 루트 CA의 공개키로 검증합니다. 루트 CA의 공개키는 브라우저에 내장되어 있으므로 최종적으로 신뢰가 확보됩니다.

    이렇게 루트부터 서버까지 이어지는 구조를 인증서 체인(Certificate Chain)이라고 합니다. 가끔 서버 설정에서 중간 인증서를 빠뜨리면 일부 브라우저에서 인증서 오류가 나는 경우가 있습니다. Let’s Encrypt와 Certbot을 사용하면 이 체인 설정을 자동으로 처리해주기 때문에 직접 신경 쓸 일은 많지 않지만, 문제가 생겼을 때 원인을 파악하려면 이 구조를 알아두는 게 좋습니다.

    HTTPS에서 실제로 일어나는 일

    브라우저에서 https://todayfunplay.com에 접속하면 위에서 설명한 모든 과정이 순식간에 일어납니다. TLS 핸드셰이크는 보통 수십 밀리초 안에 끝납니다. 사용자 입장에서는 아무것도 느끼지 못하지만, 그 짧은 시간 안에 서버의 신원 확인, 키 교환, 대칭키 생성이 전부 처리됩니다.

    핸드셰이크가 끝나면 이후의 모든 HTTP 요청과 응답이 대칭키로 암호화되어 오갑니다. URL 경로, 헤더, 본문 전부 암호화됩니다. 중간에서 패킷을 가로채더라도 암호화된 데이터만 보이기 때문에 내용을 알 수 없습니다.

    다만 접속하려는 도메인 자체는 핸드셰이크 초기 단계에서 평문으로 노출됩니다. 이걸 SNI(Server Name Indication)라고 하는데, 하나의 서버에서 여러 도메인을 호스팅할 때 어떤 도메인의 인증서를 보내야 하는지 알려주는 역할을 합니다. 어떤 사이트에 접속하는지는 보이지만 그 안에서 어떤 페이지를 보는지나 어떤 데이터를 주고받는지는 보이지 않습니다. 이 부분도 암호화하는 ECH(Encrypted Client Hello)라는 기술이 개발 중입니다.

    SSL/TLS 관련 서버 설정 확인

    이전 글에서 Certbot으로 인증서를 적용한 뒤에 Nginx 설정 파일을 보면 Certbot이 추가한 SSL 관련 설정이 보입니다. ssl_certificate와 ssl_certificate_key가 인증서 파일과 개인키 파일의 경로입니다. ssl_protocols에는 허용할 TLS 버전이 적혀 있습니다.

    보안을 위해 TLS 1.0과 1.1은 비활성화하고 TLS 1.2와 1.3만 허용하는 게 좋습니다. 최신 Certbot을 사용했다면 이미 이렇게 설정되어 있을 가능성이 높지만 한 번 확인해보는 걸 추천합니다.

    서버의 SSL 설정 상태를 외부에서 확인하고 싶으면 SSL Labs의 SSL Server Test 사이트에서 도메인을 입력하면 됩니다. 인증서 상태, TLS 버전, 암호화 스위트, 알려진 취약점 여부를 분석해서 A부터 F까지 등급을 매겨줍니다. A 이상이 나오면 안심해도 됩니다.

    마무리

    SSL/TLS는 결국 두 가지 문제를 해결합니다. 첫째, 통신하는 상대방이 진짜 그 서버가 맞는지 확인하는 것이고, 둘째, 주고받는 데이터를 제3자가 볼 수 없도록 암호화하는 것입니다. 비대칭키로 안전하게 대칭키를 교환하고, 대칭키로 빠르게 데이터를 암호화하는 구조입니다. 인증서는 서버의 공개키가 진짜인지 CA가 보증하는 역할을 합니다. 개념이 복잡하게 느껴질 수 있지만 핵심은 이겁니다. 인증서로 신원을 확인하고, 키를 교환하고, 암호화된 통신을 시작한다. 다음 글에서는 네트워크 기초에서 중요한 서브넷과 CIDR 표기법에 대해 다뤄보겠습니다.

  • WebSocket이란 무엇인가? HTTP와의 차이

    WebSocket이란 무엇인가? HTTP와의 차이

    채팅 앱을 쓰다 보면 상대방이 메시지를 보내는 순간 바로 화면에 뜹니다. 새로고침을 누르지도 않았는데 실시간으로 내용이 나타납니다. 주식 차트를 보고 있으면 가격이 쉴 새 없이 바뀌고, 온라인 게임에서는 상대방의 움직임이 즉시 내 화면에 반영됩니다. 이런 실시간 통신은 우리가 평소에 사용하는 HTTP만으로는 구현하기가 어렵습니다. 여기서 등장하는 게 WebSocket입니다. 이번 글에서는 WebSocket이 무엇인지, HTTP와 어떻게 다른지, 어떤 상황에서 쓰는 건지를 정리해보겠습니다.

    HTTP 통신의 기본 구조부터 다시 보기

    WebSocket을 이해하려면 먼저 HTTP가 어떤 방식으로 동작하는지를 다시 짚어야 합니다.

    HTTP는 요청과 응답으로 이루어진 통신 방식입니다. 클라이언트가 서버에 요청을 보내면 서버가 응답을 돌려주고, 그걸로 통신이 끝납니다. 브라우저에서 웹 페이지를 열면 HTML을 달라고 요청하고, 서버가 HTML을 보내주고, 연결이 끊어집니다. 이미지가 필요하면 다시 요청하고, CSS가 필요하면 또 요청합니다. 매번 요청이 있어야 응답이 오는 구조입니다.

    이 방식의 핵심적인 특징은 서버가 먼저 클라이언트에게 데이터를 보낼 수 없다는 점입니다. 서버 쪽에서 새로운 데이터가 생겨도 클라이언트가 요청하지 않는 한 전달할 방법이 없습니다. 항상 클라이언트가 먼저 물어봐야 합니다.

    블로그 글을 읽거나 쇼핑몰에서 상품을 조회하는 정도의 서비스에서는 이게 아무 문제가 없습니다. 사용자가 클릭할 때마다 새 페이지를 요청하면 되니까요. 하지만 실시간으로 데이터가 계속 바뀌는 서비스에서는 이 방식이 한계에 부딪힙니다.

    HTTP로 실시간을 흉내내는 방법과 그 한계

    WebSocket이 나오기 전에도 실시간 비슷한 경험을 만들려는 시도가 있었습니다.

    가장 단순한 방법은 폴링(Polling)입니다. 클라이언트가 일정 간격으로 서버에 반복 요청을 보내는 겁니다. 예를 들어 3초마다 “새 메시지 있어?”라고 물어보는 식입니다. 구현은 간단하지만 문제가 많습니다. 새 데이터가 없어도 계속 요청을 보내기 때문에 서버에 불필요한 부하가 걸립니다. 3초 간격이면 실시간이라고 보기도 어렵습니다. 간격을 짧게 하면 서버 부담이 더 커집니다.

    롱 폴링(Long Polling)이라는 개선된 방식도 있습니다. 클라이언트가 요청을 보내면 서버가 바로 응답하지 않고 새 데이터가 생길 때까지 연결을 유지합니다. 데이터가 생기면 그때 응답을 보내고, 클라이언트는 응답을 받자마자 다시 요청을 겁니다. 폴링보다는 낫지만 매번 연결을 새로 맺어야 하고, HTTP 헤더가 계속 오가기 때문에 여전히 비효율적입니다.

    이런 방법들이 근본적으로 비효율적인 이유는 HTTP 자체가 단방향 통신을 전제로 설계되었기 때문입니다. 양방향 실시간 통신을 하려면 아예 다른 방식이 필요했고, 그래서 나온 게 WebSocket입니다.

    WebSocket이란 무엇인가

    WebSocket은 클라이언트와 서버 사이에 하나의 연결을 열어두고, 그 연결을 통해 양쪽이 자유롭게 데이터를 주고받을 수 있는 통신 프로토콜입니다.

    HTTP가 편지를 주고받는 것이라면, WebSocket은 전화 통화에 가깝습니다. 편지는 한쪽이 보내면 상대방이 읽고 답장을 써서 보내야 합니다. 매번 새 편지를 써야 하고, 상대방이 먼저 말을 걸 수 없습니다. 반면에 전화는 한번 연결되면 양쪽이 언제든 말할 수 있습니다. 상대방이 할 말이 생기면 바로 전달할 수 있고, 대화가 끝날 때까지 연결이 유지됩니다.

    WebSocket도 마찬가지입니다. 처음에 연결을 맺으면 그 연결이 계속 유지되고, 서버도 클라이언트에게 먼저 데이터를 보낼 수 있습니다. 클라이언트가 요청하지 않아도 서버에서 새 데이터가 생기면 즉시 밀어넣을 수 있습니다. 이걸 전이중 통신(Full-Duplex)이라고 합니다.

    WebSocket 연결이 맺어지는 과정

    WebSocket 연결은 처음에 HTTP를 통해 시작됩니다. 이 과정을 핸드셰이크(Handshake)라고 합니다.

    클라이언트가 서버에 HTTP 요청을 보내면서 “이 연결을 WebSocket으로 업그레이드하고 싶다”는 헤더를 포함합니다. 서버가 WebSocket을 지원하면 “좋다, 업그레이드하자”라고 응답합니다. 이 응답의 상태 코드는 101 Switching Protocols입니다. 이 순간부터 HTTP 연결이 WebSocket 연결로 전환되고, 이후부터는 HTTP가 아닌 WebSocket 프로토콜로 데이터를 주고받습니다.

    핸드셰이크 요청을 간단하게 보면 이런 형태입니다.

    GET /chat HTTP/1.1
    Host: today-play.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13

    Upgrade: websocket이라는 헤더가 핵심입니다. 서버가 이걸 받아들이면 연결이 전환됩니다. 처음에 HTTP로 시작하기 때문에 기존의 웹 인프라(방화벽, 프록시 등)를 그대로 통과할 수 있다는 장점이 있습니다. 포트도 HTTP와 같은 80번, HTTPS와 같은 443번을 사용합니다. WebSocket의 URL 스킴은 ws와 wss인데, ws는 HTTP에 대응하고 wss는 HTTPS에 대응합니다.

    HTTP와 WebSocket의 차이 정리

    두 프로토콜의 차이를 항목별로 비교하면 이렇습니다.

    통신 방향에서 HTTP는 클라이언트가 요청해야 서버가 응답하는 단방향 구조입니다. WebSocket은 연결이 맺어진 후 양쪽이 언제든 데이터를 보낼 수 있는 양방향 구조입니다.

    연결 유지에서 HTTP는 기본적으로 요청-응답이 끝나면 연결이 종료됩니다. keep-alive로 연결을 재사용할 수는 있지만 통신 방식 자체가 바뀌는 건 아닙니다. WebSocket은 한번 연결되면 명시적으로 끊기 전까지 계속 유지됩니다.

    오버헤드에서 HTTP는 매 요청마다 헤더 정보가 포함됩니다. 쿠키, 인증 정보, 콘텐츠 타입 같은 헤더가 매번 오갑니다. 간단한 데이터 하나 보내는데 헤더가 수백 바이트에서 수 킬로바이트까지 될 수 있습니다. WebSocket은 핸드셰이크 이후에는 프레임 단위로 데이터를 보내는데 프레임 헤더가 2바이트에서 14바이트 정도로 매우 작습니다. 작은 데이터를 자주 주고받아야 하는 상황에서 효율 차이가 큽니다.

    실시간성에서 HTTP 폴링은 요청 간격만큼의 지연이 생깁니다. WebSocket은 데이터가 생기는 즉시 전달되기 때문에 사실상 지연이 없습니다.

    WebSocket을 사용하는 대표적인 서비스들

    채팅 애플리케이션이 가장 대표적입니다. 카카오톡이나 슬랙 같은 메신저에서 메시지가 실시간으로 오가는 건 WebSocket 덕분입니다. 상대방이 메시지를 보내면 서버가 바로 내 클라이언트에 전달합니다.

    실시간 주식 차트나 가상화폐 거래소도 WebSocket을 많이 씁니다. 가격이 밀리초 단위로 변하기 때문에 폴링으로는 따라갈 수 없습니다. 서버에서 가격이 변할 때마다 연결된 모든 클라이언트에 즉시 전달합니다.

    온라인 게임에서 플레이어들의 위치나 행동을 실시간으로 동기화하는 데도 사용됩니다. 협업 도구에서 여러 사람이 동시에 문서를 편집할 때 다른 사람의 커서 위치나 수정 내용이 바로 반영되는 것도 WebSocket 기반인 경우가 많습니다.

    알림 시스템에도 적합합니다. 새 댓글이 달렸거나 주문 상태가 바뀌었을 때 서버가 먼저 클라이언트에 알려줄 수 있습니다.

    간단한 WebSocket 코드 예시

    JavaScript에서 WebSocket을 사용하는 코드는 생각보다 간단합니다.

    클라이언트 쪽 코드입니다.

    javascript

    const socket = new WebSocket('wss://today-play.com/chat');
    
    socket.onopen = function() {
        console.log('서버에 연결되었습니다');
        socket.send('안녕하세요');
    };
    
    socket.onmessage = function(event) {
        console.log('서버에서 받은 메시지:', event.data);
    };
    
    socket.onclose = function() {
        console.log('연결이 종료되었습니다');
    };

    new WebSocket으로 연결을 맺고, onopen에서 연결 성공 시 처리를 하고, send로 서버에 메시지를 보내고, onmessage에서 서버가 보낸 메시지를 받습니다. HTTP처럼 매번 요청을 보내는 게 아니라 이벤트 기반으로 동작합니다. 서버에서 메시지가 오면 onmessage가 자동으로 호출됩니다.

    서버 쪽은 Node.js 환경에서 ws 라이브러리를 사용하면 이런 식입니다.

    javascript

    const WebSocket = require('ws');
    const server = new WebSocket.Server({ port: 8080 });
    
    server.on('connection', function(socket) {
        console.log('클라이언트가 연결되었습니다');
    
        socket.on('message', function(message) {
            console.log('받은 메시지:', message);
            socket.send('메시지를 받았습니다: ' + message);
        });
    
        socket.on('close', function() {
            console.log('클라이언트 연결이 종료되었습니다');
        });
    });

    서버도 마찬가지로 이벤트 기반입니다. 클라이언트가 연결되면 connection 이벤트가 발생하고, 메시지가 오면 message 이벤트가 발생합니다. socket.send로 클라이언트에게 언제든 데이터를 보낼 수 있습니다.

    WebSocket을 사용할 때 주의할 점

    WebSocket 연결은 계속 유지되기 때문에 서버 입장에서는 연결된 클라이언트 수만큼 리소스를 잡고 있어야 합니다. 동시 접속자가 많아지면 서버의 메모리와 파일 디스크립터가 부족해질 수 있습니다. HTTP는 요청을 처리하고 바로 연결을 해제하기 때문에 같은 수의 사용자를 처리할 때 WebSocket이 더 많은 리소스를 사용합니다.

    네트워크가 불안정하면 연결이 끊어질 수 있습니다. 모바일 환경에서 와이파이가 바뀌거나 지하철에서 신호가 약해지면 WebSocket 연결이 끊깁니다. 그래서 클라이언트 코드에 자동 재연결 로직을 넣어두는 게 일반적입니다. 연결이 끊어지면 일정 시간 후에 다시 연결을 시도하는 식입니다.

    로드 밸런서를 사용하는 환경에서는 WebSocket 연결이 특정 서버에 고정되어야 합니다. HTTP는 요청마다 다른 서버로 보내도 상관없지만, WebSocket은 연결이 유지되는 동안 같은 서버와 통신해야 합니다. 이걸 스티키 세션(Sticky Session)이라고 하는데, 로드 밸런서 설정에서 별도로 처리해야 합니다.

    보안 측면에서는 반드시 wss를 사용해야 합니다. ws는 암호화되지 않은 연결이고 wss는 TLS로 암호화된 연결입니다. HTTPS를 적용한 사이트에서 ws로 연결하려고 하면 브라우저가 차단합니다.

    WebSocket 대신 쓸 수 있는 대안들

    모든 실시간 통신에 WebSocket이 필요한 건 아닙니다.

    SSE(Server-Sent Events)는 서버에서 클라이언트로 단방향으로 데이터를 보내는 방식입니다. 클라이언트가 서버에 데이터를 보낼 필요 없이 서버의 업데이트만 받으면 되는 경우에 적합합니다. 뉴스 피드 업데이트나 실시간 알림 같은 데에 쓸 수 있습니다. HTTP 위에서 동작하기 때문에 별도의 프로토콜이 필요 없고 구현이 WebSocket보다 간단합니다.

    Socket.IO는 WebSocket을 감싼 라이브러리입니다. WebSocket을 기본으로 사용하되, WebSocket이 안 되는 환경에서는 자동으로 폴링으로 전환합니다. 자동 재연결, 룸 기능, 브로드캐스트 같은 편의 기능이 내장되어 있어서 실무에서 많이 사용됩니다. 다만 Socket.IO는 순수 WebSocket과 호환되지 않는 자체 프로토콜을 사용한다는 점을 알아둬야 합니다.

    그러니까 양방향 실시간 통신이 필요하면 WebSocket, 서버에서 클라이언트로의 단방향 푸시만 필요하면 SSE, WebSocket에 편의 기능까지 필요하면 Socket.IO를 검토하면 됩니다.

    마무리

    WebSocket은 HTTP의 요청-응답 구조로는 해결할 수 없는 실시간 양방향 통신을 가능하게 해주는 프로토콜입니다. 한번 연결을 맺으면 서버와 클라이언트가 자유롭게 데이터를 주고받을 수 있고, 매 요청마다 헤더를 주고받는 오버헤드도 없습니다. 모든 웹 서비스에 WebSocket이 필요한 건 아니지만, 채팅이나 실시간 알림처럼 서버가 먼저 데이터를 보내야 하는 기능을 만들 때는 사실상 필수입니다. 다음 글에서는 HTTPS의 기반이 되는 SSL/TLS의 동작 원리를 좀 더 자세히 다뤄보겠습니다.

  • 서버 모니터링 도구 비교 — top, htop, Grafana 등

    서버 모니터링 도구 비교 — top, htop, Grafana 등

    서버를 운영하다 보면 갑자기 사이트가 느려지거나 접속이 안 되는 상황이 생깁니다. 이때 가장 먼저 해야 할 일은 서버 상태를 확인하는 겁니다. CPU가 과부하인지, 메모리가 부족한지, 디스크가 꽉 찼는지를 알아야 원인을 찾을 수 있습니다. 리눅스에는 서버 상태를 확인할 수 있는 도구가 여러 가지 있는데, 터미널에서 바로 쓸 수 있는 간단한 것부터 웹 대시보드로 보는 본격적인 모니터링 시스템까지 다양합니다. 이번 글에서는 실무에서 자주 쓰이는 모니터링 도구들을 하나씩 살펴보고 어떤 상황에서 어떤 도구를 쓰면 좋은지 정리해보겠습니다.

    top — 리눅스에 기본으로 들어있는 모니터링 도구

    top은 리눅스라면 어디에나 설치되어 있는 기본 도구입니다. 별도 설치 없이 터미널에서 top을 입력하면 바로 실행됩니다.

    top

    실행하면 화면 상단에 시스템 전체 요약 정보가 나옵니다. 서버가 켜진 시간, 현재 접속 중인 사용자 수, 로드 애버리지, 전체 CPU 사용률, 메모리 사용량이 표시됩니다. 아래쪽에는 실행 중인 프로세스 목록이 CPU 사용률 순으로 나열됩니다.

    로드 애버리지는 서버 상태를 빠르게 판단할 때 유용한 지표입니다. 세 개의 숫자가 나오는데 각각 1분, 5분, 15분 평균입니다. 이 숫자가 서버의 CPU 코어 수보다 높으면 서버에 부하가 걸리고 있다는 뜻입니다. 코어가 4개인 서버에서 로드 애버리지가 4를 넘으면 처리 대기 중인 작업이 쌓이고 있는 상태입니다.

    top 화면에서 특정 작업을 할 수도 있습니다. Shift+M을 누르면 메모리 사용량 순으로 정렬되고, Shift+P를 누르면 다시 CPU 순으로 돌아옵니다. 숫자 1을 누르면 CPU 코어별 사용률을 개별로 볼 수 있습니다. 특정 프로세스를 종료하고 싶으면 k를 누르고 PID를 입력하면 됩니다. 나가려면 q를 누릅니다.

    top의 장점은 어디서든 바로 쓸 수 있다는 겁니다. SSH로 접속해서 서버 상태를 빠르게 확인할 때 가장 먼저 사용하는 도구입니다. 다만 화면이 투박하고 프로세스를 시각적으로 구분하기가 어렵습니다. 정보는 다 있는데 한눈에 파악하기가 불편합니다.

    htop — top의 개선 버전

    htop은 top과 같은 역할을 하지만 화면이 훨씬 보기 좋습니다. 색상이 들어가 있고 CPU와 메모리 사용량이 막대그래프로 표시되어서 직관적입니다.

    Ubuntu에서 설치하려면 이렇게 합니다.

    sudo apt install htop

    설치 후 htop을 입력하면 실행됩니다.

    htop

    화면 상단에 CPU 코어별 사용률이 색깔 막대로 나옵니다. 초록색은 사용자 프로세스, 빨간색은 커널 프로세스, 파란색은 저우선순위 프로세스입니다. 메모리와 스왑 사용량도 막대로 표시됩니다. top에서는 숫자만 보고 머릿속으로 계산해야 했던 걸 htop에서는 눈으로 바로 확인할 수 있습니다.

    프로세스 목록도 top보다 다루기 편합니다. 마우스로 클릭해서 프로세스를 선택할 수 있고, F6을 눌러서 정렬 기준을 바꿀 수 있고, F9를 눌러서 프로세스에 시그널을 보낼 수 있습니다. 트리 뷰도 지원하는데 F5를 누르면 프로세스의 부모-자식 관계를 트리 구조로 보여줍니다. Nginx 마스터 프로세스 아래에 워커 프로세스들이 달려 있는 걸 한눈에 볼 수 있어서 프로세스 구조를 파악하기 좋습니다.

    F2를 누르면 설정 화면이 나오고 표시할 항목을 커스터마이즈할 수 있습니다. 화면 하단에 기능 키 안내가 나오기 때문에 단축키를 외우지 않아도 됩니다.

    개인적으로 서버에 접속하면 top 대신 htop을 먼저 치게 됩니다. 설치가 간단하고 정보량은 top과 같은데 가독성이 훨씬 좋기 때문입니다. 새 서버를 세팅하면 거의 습관적으로 htop부터 설치합니다.

    vmstat — 시스템 자원을 숫자로 빠르게 확인

    vmstat은 CPU, 메모리, 스왑, I/O 상태를 한 줄로 보여주는 도구입니다. top이나 htop처럼 실시간으로 갱신되는 화면이 아니라 스냅샷을 찍어주는 느낌입니다.

    vmstat 2 5

    이 명령어는 2초 간격으로 5번 측정해서 보여줍니다. 출력 결과를 보면 procs, memory, swap, io, system, cpu 항목이 있습니다.

    여기서 주로 보는 건 몇 가지입니다. r 값은 CPU를 기다리고 있는 프로세스 수인데 이게 코어 수보다 많으면 CPU가 부족한 겁니다. si와 so는 스왑 인과 스왑 아웃인데 이 값이 지속적으로 높으면 메모리가 부족해서 디스크를 메모리처럼 쓰고 있다는 뜻입니다. 스왑이 많이 발생하면 서버가 급격하게 느려집니다.

    vmstat은 서버에 문제가 생겼을 때 원인이 CPU인지 메모리인지 디스크 I/O인지를 빠르게 판별하는 데 유용합니다. 스크립트에서 결과를 파싱하기도 편해서 모니터링 자동화에 활용하기도 합니다.

    df와 du — 디스크 용량 확인

    서버가 갑자기 이상해지는 원인 중 의외로 많은 게 디스크 용량 부족입니다. 로그 파일이 쌓여서 디스크가 꽉 차면 데이터베이스가 멈추거나 새 파일을 생성하지 못해서 서비스가 중단될 수 있습니다.

    df는 파일 시스템별 디스크 사용량을 보여줍니다.

    df -h

    -h 옵션을 붙이면 사람이 읽기 편한 단위(GB, MB)로 표시됩니다. Use% 칼럼이 90%를 넘어가면 조치가 필요합니다.

    du는 특정 디렉토리가 얼마나 많은 용량을 차지하는지 확인할 때 씁니다.

    du -sh /var/log/*

    -s는 요약, -h는 사람이 읽기 편한 단위입니다. 어떤 디렉토리가 용량을 많이 잡아먹고 있는지 찾을 때 유용합니다. 로그 디렉토리에서 몇 기가짜리 파일이 발견되면 이전 글에서 다뤘던 크론탭으로 주기적으로 정리하는 설정을 추가하면 됩니다.

    iotop과 nethogs — 디스크와 네트워크 사용 추적

    CPU와 메모리는 정상인데 서버가 느리다면 디스크 I/O나 네트워크가 병목일 수 있습니다.

    iotop은 어떤 프로세스가 디스크를 많이 읽고 쓰고 있는지 보여줍니다.

    sudo apt install iotop
    sudo iotop

    htop이 CPU와 메모리 기준으로 프로세스를 보여준다면 iotop은 디스크 I/O 기준으로 보여줍니다. 데이터베이스가 디스크를 과도하게 사용하고 있다거나 백업 작업이 I/O를 잡아먹고 있다거나 하는 걸 확인할 수 있습니다.

    nethogs는 프로세스별 네트워크 사용량을 보여줍니다.

    sudo apt install nethogs
    sudo nethogs

    어떤 프로세스가 네트워크 대역폭을 많이 차지하는지 실시간으로 확인할 수 있습니다. 의도하지 않은 프로세스가 외부로 트래픽을 보내고 있다면 보안 문제일 수도 있으니 주의깊게 봐야 합니다.

    Prometheus와 Grafana — 본격적인 모니터링 시스템

    지금까지 다룬 도구들은 전부 터미널에서 서버에 직접 접속해서 확인하는 방식입니다. 서버가 한두 대일 때는 이걸로 충분하지만 서버가 여러 대이거나, 시간에 따른 추이를 보고 싶거나, 특정 조건에서 알림을 받고 싶으면 별도의 모니터링 시스템이 필요합니다.

    Prometheus는 모니터링 데이터를 수집하고 저장하는 도구입니다. 각 서버에 설치된 에이전트(Node Exporter)가 서버의 CPU, 메모리, 디스크 같은 지표를 수집하고, Prometheus가 주기적으로 이 데이터를 가져와서 시계열 데이터베이스에 저장합니다.

    Grafana는 저장된 데이터를 웹 대시보드로 시각화하는 도구입니다. Prometheus에 쌓인 데이터를 가져와서 그래프, 차트, 게이지 같은 형태로 보여줍니다. 브라우저에서 접속하면 서버의 CPU 사용률이 시간에 따라 어떻게 변했는지, 메모리가 언제부터 부족해지기 시작했는지를 시각적으로 확인할 수 있습니다.

    전체 구조를 정리하면 이렇습니다. 서버에 Node Exporter가 설치되어 지표를 노출하고, Prometheus가 이걸 수집해서 저장하고, Grafana가 Prometheus에서 데이터를 꺼내와서 대시보드에 그려줍니다. 세 가지가 각각 다른 역할을 하면서 하나의 모니터링 파이프라인을 구성합니다.

    Grafana의 가장 큰 장점은 대시보드를 자유롭게 구성할 수 있다는 점입니다. 직접 만들 수도 있고, Grafana Labs 커뮤니티에서 다른 사람들이 만들어둔 대시보드를 가져와서 바로 쓸 수도 있습니다. Node Exporter용 대시보드를 임포트하면 서버 모니터링 화면이 몇 분 만에 완성됩니다.

    알림 설정도 가능합니다. CPU 사용률이 90%를 넘으면 슬랙으로 알림을 보내거나, 디스크 사용량이 80%를 넘으면 이메일을 보내는 식으로 설정할 수 있습니다. 문제가 생겼을 때 서버에 직접 접속해서 확인하는 게 아니라 알림을 받고 대응하는 구조를 만들 수 있습니다.

    다만 Prometheus와 Grafana는 설치와 설정이 위에서 다룬 도구들에 비해 복잡합니다. 서버를 처음 운영하는 단계에서 바로 도입하기보다는 서버 운영이 어느 정도 익숙해지고, 관리할 서버가 늘어나거나 안정적인 서비스 운영이 필요해질 때 구축하는 게 현실적입니다.

    클라우드 모니터링 서비스

    AWS, GCP 같은 클라우드를 사용하고 있다면 클라우드 자체에서 제공하는 모니터링 서비스도 있습니다. AWS의 CloudWatch, GCP의 Cloud Monitoring이 대표적입니다.

    이런 서비스들은 별도 설치 없이 클라우드 콘솔에서 바로 서버의 CPU, 메모리, 네트워크, 디스크 지표를 확인할 수 있습니다. 알림 설정도 콘솔에서 클릭 몇 번으로 됩니다. 직접 Prometheus를 구축하는 것보다 훨씬 간편합니다.

    단점은 클라우드에 종속된다는 점과 세밀한 커스터마이즈가 어렵다는 점입니다. 그리고 무료 범위를 넘어가면 비용이 발생합니다. 서버가 적고 해당 클라우드를 이미 사용하고 있다면 충분히 실용적인 선택입니다.

    상황별 도구 선택 가이드

    서버에 문제가 생겨서 지금 당장 상태를 확인해야 한다면 top이나 htop을 씁니다. SSH로 접속해서 바로 확인할 수 있고 별도 설치가 필요 없거나 간단합니다.

    CPU인지 메모리인지 디스크인지 원인을 빠르게 분류하고 싶으면 vmstat을 씁니다. 디스크가 의심되면 iotop, 네트워크가 의심되면 nethogs를 추가로 확인합니다.

    디스크 용량이 문제인 것 같으면 df와 du로 어디가 꽉 찼는지 찾습니다.

    서버를 지속적으로 모니터링하면서 시간에 따른 추이를 보고 싶고 알림까지 받고 싶으면 Prometheus와 Grafana 조합을 구축합니다.

    클라우드 서버를 쓰고 있고 간편하게 모니터링하고 싶으면 CloudWatch나 Cloud Monitoring 같은 클라우드 내장 서비스를 활용합니다.

    처음에는 htop 하나만 잘 써도 대부분의 상황에 대응할 수 있습니다. 거기서 시작해서 필요에 따라 도구를 하나씩 추가해나가면 됩니다.

    마무리

    서버 모니터링은 문제가 터진 뒤에 하는 것보다 평소에 상태를 파악해두는 게 중요합니다. 정상일 때의 CPU 사용률이나 메모리 사용량을 알고 있어야 비정상인 상황을 감지할 수 있습니다. htop으로 수시로 서버 상태를 확인하는 습관을 들이고, 서비스가 커지면 Grafana 같은 시각화 도구를 도입해서 체계적으로 관리하는 방향으로 나아가면 됩니다. 다음 글에서는 HTTP와 실시간 통신의 차이를 이해할 수 있는 WebSocket에 대해 다뤄보겠습니다.

  • 크론탭(Crontab)으로 서버 작업 자동화하기

    크론탭(Crontab)으로 서버 작업 자동화하기

    서버를 운영하다 보면 매일 반복해야 하는 작업들이 생깁니다. 로그 파일을 정리하거나, 데이터베이스를 백업하거나, SSL 인증서 갱신을 확인하거나, 특정 시간에 스크립트를 돌리는 일들입니다. 이런 걸 매번 직접 접속해서 수동으로 실행하면 번거롭기도 하고 깜빡하는 날이 반드시 생깁니다. 리눅스에서는 크론(Cron)이라는 도구가 이런 반복 작업을 자동으로 처리해줍니다. 이번 글에서는 크론탭이 무엇인지, 어떻게 설정하는지, 실무에서 어떤 식으로 활용하는지를 정리해보겠습니다.

    크론이란 무엇인가

    크론은 리눅스에 기본으로 내장되어 있는 작업 스케줄러입니다. 지정한 시간에 지정한 명령어를 자동으로 실행해줍니다. 별도로 설치할 필요 없이 대부분의 리눅스 배포판에 이미 들어가 있습니다.

    크론탭(Crontab)은 크론 테이블(Cron Table)의 줄임말이고, 어떤 작업을 언제 실행할지 적어둔 설정 파일입니다. 사용자마다 자기만의 크론탭을 가질 수 있고, 시스템 전체에 적용되는 크론탭도 따로 있습니다.

    크론 데몬은 시스템이 켜져 있는 동안 백그라운드에서 항상 돌고 있으면서, 매분마다 크론탭을 확인합니다. 현재 시간과 일치하는 작업이 있으면 실행하고, 없으면 넘어갑니다. 서버가 재부팅되어도 크론 데몬은 자동으로 다시 시작됩니다.

    크론탭 기본 사용법

    크론탭을 열려면 터미널에서 다음 명령어를 입력합니다.

    crontab -e

    처음 실행하면 어떤 편집기를 사용할지 물어봅니다. nano가 익숙하다면 nano를 선택하면 됩니다. 편집기가 열리면 여기에 작업 스케줄을 한 줄씩 추가하면 됩니다.

    현재 등록된 크론탭을 확인하고 싶으면 이렇게 합니다.

    crontab -l

    크론탭을 전부 삭제하고 싶으면 이렇게 합니다. 주의해서 사용해야 합니다.

    crontab -r

    다른 사용자의 크론탭을 편집하려면 sudo와 -u 옵션을 사용합니다.

    sudo crontab -u www-data -e

    크론탭 시간 형식 이해하기

    크론탭에서 가장 중요한 건 시간 형식입니다. 한 줄에 하나의 작업을 쓰는데, 형식은 이렇습니다.

    분 시 일 월 요일 실행할명령어

    각 자리에 들어갈 수 있는 값의 범위는 이렇습니다. 분은 0부터 59까지, 시는 0부터 23까지, 일은 1부터 31까지, 월은 1부터 12까지, 요일은 0부터 7까지인데 0과 7이 둘 다 일요일입니다.

    별표는 “매번”이라는 뜻입니다. 해당 자리에 별표를 넣으면 그 단위의 모든 값에 해당된다는 의미입니다.

    예시를 보면 바로 감이 옵니다.

    0 3 * * * /home/ubuntu/backup.sh

    이건 매일 새벽 3시 정각에 backup.sh를 실행하라는 뜻입니다. 분이 0이고 시가 3이고, 일 월 요일이 전부 별표이니까 매일 반복됩니다.

    30 2 * * 0 /home/ubuntu/weekly-cleanup.sh

    이건 매주 일요일 새벽 2시 30분에 실행하라는 뜻입니다. 요일 자리에 0이 들어갔으니 일요일입니다.

    */10 * * * * /home/ubuntu/check-server.sh

    슬래시는 간격을 의미합니다. */10은 10분마다라는 뜻입니다. 즉 매 10분마다 서버 상태를 확인하는 스크립트를 실행합니다.

    0 9-18 * * 1-5 /home/ubuntu/business-report.sh

    하이픈은 범위를 나타냅니다. 시 자리에 9-18이면 오전 9시부터 오후 6시까지이고, 요일 자리에 1-5면 월요일부터 금요일까지입니다. 평일 업무시간에만 매시 정각에 실행되는 설정입니다.

    0 0 1,15 * * /home/ubuntu/biweekly-task.sh

    쉼표는 특정 값 여러 개를 지정할 때 씁니다. 일 자리에 1,15이면 매월 1일과 15일 자정에 실행합니다.

    실무에서 자주 쓰는 크론탭 예시

    데이터베이스 백업은 크론탭으로 가장 많이 자동화하는 작업입니다. MySQL 데이터베이스를 매일 새벽 4시에 백업하는 설정은 이렇습니다.

    0 4 * * * mysqldump -u root -p'비밀번호' mydb > /backup/mydb_$(date +\%Y\%m\%d).sql

    date 명령어를 넣으면 파일 이름에 날짜가 들어가서 매일 다른 파일로 저장됩니다. 크론탭에서 퍼센트 기호는 특수 문자로 취급되기 때문에 백슬래시로 이스케이프해야 합니다. 이걸 모르면 명령어가 제대로 동작하지 않아서 한참 헤매는 경우가 있습니다.

    오래된 로그 파일을 정리하는 것도 흔합니다. 30일 이상 된 로그 파일을 매주 일요일에 삭제하는 설정입니다.

    0 5 * * 0 find /var/log/myapp/ -name "*.log" -mtime +30 -delete

    find 명령어의 -mtime +30은 수정된 지 30일이 지난 파일을 찾는다는 뜻이고, -delete는 찾은 파일을 삭제합니다.

    서버가 정상적으로 돌고 있는지 주기적으로 확인하는 헬스체크도 자주 설정합니다.

    */5 * * * * curl -s -o /dev/null -w "%{http_code}" https://todayfunplay.com | grep -q "200" || echo "사이트 다운" >> /var/log/healthcheck.log

    5분마다 사이트에 요청을 보내서 응답 코드가 200이 아니면 로그에 기록하는 설정입니다. 간단한 모니터링 용도로 쓸 수 있습니다.

    이전 글에서 다뤘던 Let’s Encrypt 인증서 자동 갱신도 크론탭으로 설정합니다.

    0 3 * * * certbot renew --quiet

    매일 새벽 3시에 인증서 만료를 확인하고 필요하면 갱신합니다.

    크론탭 작성 시 주의할 점

    크론탭에서 가장 많이 실수하는 부분이 경로 문제입니다. 크론은 사용자가 직접 로그인한 셸 환경과 다른 환경에서 실행됩니다. 터미널에서는 잘 되는 명령어가 크론에서는 안 되는 경우가 흔한데, 환경 변수와 PATH가 다르기 때문입니다.

    해결 방법은 간단합니다. 크론탭에서 명령어를 쓸 때는 항상 절대 경로를 사용하면 됩니다.

    # 이렇게 하면 안 될 수 있음
    * * * * * node /home/ubuntu/app.js
    
    # 이렇게 절대 경로를 써야 확실함
    * * * * * /usr/bin/node /home/ubuntu/app.js

    명령어의 절대 경로를 모르겠으면 which 명령어로 확인할 수 있습니다.

    which node
    /usr/bin/node

    스크립트 파일을 실행할 때는 실행 권한이 있는지도 확인해야 합니다. 이전 글에서 다뤘던 chmod로 실행 권한을 줘야 합니다.

    chmod +x /home/ubuntu/backup.sh

    크론탭에서 퍼센트 기호를 쓸 때는 반드시 백슬래시로 이스케이프해야 한다는 것도 중요합니다. 크론에서 퍼센트 기호는 줄바꿈으로 해석되기 때문에 이스케이프하지 않으면 명령어가 잘려서 실행됩니다.

    크론 실행 결과 확인하기

    크론이 제대로 동작하는지 확인하는 방법을 모르면 문제가 생겨도 알 수가 없습니다.

    가장 기본적인 방법은 시스템 로그를 확인하는 겁니다.

    grep CRON /var/log/syslog

    이 명령어를 치면 크론이 실행한 작업의 로그가 나옵니다. 어떤 사용자의 어떤 명령어가 언제 실행됐는지 확인할 수 있습니다.

    크론 작업의 출력 결과를 파일로 저장하고 싶으면 리다이렉션을 사용합니다.

    0 3 * * * /home/ubuntu/backup.sh >> /var/log/backup.log 2>&1

    는 출력을 파일에 추가하는 것이고, 2>&1은 에러 출력도 같은 파일에 저장하라는 뜻입니다. 이렇게 설정해두면 백업 스크립트의 실행 결과와 에러 메시지를 전부 로그 파일에서 확인할 수 있습니다.

    반대로 출력이 필요 없으면 /dev/null로 보내서 버릴 수 있습니다.

    0 3 * * * /home/ubuntu/backup.sh > /dev/null 2>&1

    크론은 기본적으로 작업의 출력이 있으면 시스템 메일로 보냅니다. 메일 설정이 안 되어 있으면 “You have new mail”이라는 메시지가 계속 쌓일 수 있는데, 위처럼 출력을 /dev/null로 보내면 이 문제가 해결됩니다.

    시스템 크론과 사용자 크론의 차이

    지금까지 다룬 건 사용자 크론탭입니다. crontab -e로 편집하는 것이고, 해당 사용자의 권한으로 실행됩니다.

    시스템 전체에 적용되는 크론 설정은 /etc/crontab 파일에 있습니다. 이 파일은 사용자 크론탭과 형식이 약간 다릅니다.

    0 3 * * * root /home/ubuntu/backup.sh

    시간 형식과 명령어 사이에 실행할 사용자 이름이 들어갑니다. 시스템 크론탭에서는 어떤 사용자 권한으로 실행할지 직접 지정해야 합니다.

    /etc/cron.d/ 디렉토리에 개별 파일로 넣을 수도 있고, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/ 디렉토리에 스크립트를 넣으면 이름 그대로 매일, 매주, 매월 자동 실행됩니다. 이 디렉토리들은 시간을 세밀하게 지정할 필요 없이 스크립트만 넣으면 되기 때문에 편합니다.

    크론 대신 사용할 수 있는 것들

    크론은 단순한 반복 작업에는 충분하지만 한계가 있습니다. 작업 간 의존 관계를 설정하거나, 실패했을 때 재시도 로직을 넣거나, 실행 상태를 웹에서 모니터링하는 건 크론만으로는 어렵습니다.

    이런 경우에는 systemd timer를 사용하는 방법이 있습니다. systemd는 리눅스의 서비스 관리 도구인데 타이머 기능도 가지고 있습니다. 크론보다 설정이 복잡하지만 서비스와 연동이 쉽고 로그 관리가 편합니다.

    더 복잡한 작업 스케줄링이 필요하면 Jenkins, Airflow 같은 전문 도구를 쓰기도 합니다. 하지만 서버 한두 대를 운영하면서 기본적인 자동화를 하는 수준이라면 크론탭만으로 충분합니다.

    마무리

    크론탭은 리눅스 서버 운영에서 가장 기본적이면서도 실용적인 도구입니다. 시간 형식 다섯 자리와 실행할 명령어 한 줄이면 어떤 작업이든 자동화할 수 있습니다. 주의할 점은 절대 경로를 사용하는 것, 실행 권한을 확인하는 것, 로그를 남겨두는 것 정도입니다. 서버를 운영하기 시작했다면 백업과 로그 정리부터 크론탭으로 자동화해보는 걸 추천합니다. 다음 글에서는 서버 상태를 실시간으로 확인할 수 있는 모니터링 도구들을 비교해보겠습니다.

  • 리눅스 파일 권한(chmod, chown) 쉽게 이해하기

    리눅스 파일 권한(chmod, chown) 쉽게 이해하기

    리눅스 서버를 운영하다 보면 “Permission denied”라는 에러를 한 번쯤은 만나게 됩니다. 분명 파일이 거기 있는데 열 수 없다거나, 스크립트를 실행하려는데 권한이 없다고 나오는 경우입니다. 이런 문제가 생기면 보통 chmod나 chown 명령어를 써서 해결하라는 답변이 나옵니다. 그런데 명령어만 따라 치면 당장은 해결되지만 왜 그렇게 하는 건지 이해하지 못하면 나중에 또 같은 문제를 겪게 됩니다. 이번 글에서는 리눅스 파일 권한이 어떤 구조로 되어 있는지, chmod와 chown이 각각 무엇을 바꾸는 건지를 기초부터 정리해보겠습니다.

    리눅스는 왜 파일마다 권한을 나누는가

    리눅스는 태생부터 여러 사용자가 동시에 사용하는 운영체제입니다. 서버 한 대에 여러 명이 접속해서 작업하는 환경을 전제로 설계되었기 때문에, 내 파일을 다른 사용자가 함부로 읽거나 수정하거나 삭제하지 못하도록 파일 하나하나에 권한이 붙어 있습니다.

    서버 환경에서도 마찬가지입니다. 웹 서버 프로세스는 www-data라는 사용자 권한으로 실행되고, 배포 작업은 deploy 같은 사용자로 하고, 시스템 관리는 root로 합니다. 이들이 접근할 수 있는 파일 범위가 다르기 때문에 권한 설정이 중요합니다. 권한을 잘못 설정하면 웹 서버가 파일을 못 읽어서 사이트가 안 뜨거나, 반대로 너무 열어두면 보안에 구멍이 생깁니다.

    파일 권한 확인하는 방법

    터미널에서 ls -l 명령어를 치면 파일 목록과 함께 권한 정보가 나옵니다.

    ls -l
    -rw-r--r-- 1 ubuntu ubuntu  1024 Jun 15 10:30 index.html
    drwxr-xr-x 2 ubuntu ubuntu  4096 Jun 15 10:30 images
    -rwxr-xr-- 1 root   root     512 Jun 15 10:30 deploy.sh

    왼쪽에 나오는 -rw-r–r– 같은 문자열이 권한 표시입니다. 처음에는 이게 무슨 암호처럼 보이지만 규칙을 알면 간단합니다.

    맨 앞 한 글자는 파일 종류입니다. -는 일반 파일이고 d는 디렉토리입니다. 그 뒤로 9글자가 권한인데, 3글자씩 끊어서 읽으면 됩니다.

    첫 번째 세 글자는 소유자(owner)의 권한입니다. 두 번째 세 글자는 그룹(group)의 권한입니다. 세 번째 세 글자는 기타 사용자(others)의 권한입니다.

    각 자리에는 r, w, x 또는 -가 들어갑니다. r은 읽기(read), w는 쓰기(write), x는 실행(execute)입니다. -는 해당 권한이 없다는 뜻입니다.

    그래서 -rw-r–r–을 해석하면 이렇습니다. 소유자는 읽기와 쓰기가 가능하고, 그룹은 읽기만 가능하고, 기타 사용자도 읽기만 가능합니다. 아무도 실행 권한은 없습니다.

    소유자와 그룹이란

    ls -l 결과에서 ubuntu ubuntu라고 나오는 부분이 소유자와 그룹입니다. 앞의 ubuntu가 파일 소유자이고, 뒤의 ubuntu가 파일이 속한 그룹입니다.

    리눅스에서 모든 파일은 반드시 하나의 소유자와 하나의 그룹에 속합니다. 소유자는 보통 파일을 만든 사용자이고, 그룹은 그 사용자가 속한 기본 그룹이 됩니다.

    왜 그룹이라는 개념이 필요한지 의문이 들 수 있습니다. 예를 들어 개발팀 서버에서 세 명의 개발자가 같은 프로젝트 파일을 수정해야 한다고 합시다. 파일 소유자는 한 명만 될 수 있으니까, 나머지 두 명은 기타 사용자(others) 권한으로 접근해야 합니다. 그런데 others 권한을 열어두면 서버에 접속 가능한 모든 사용자에게 열리는 겁니다. 이때 세 명을 같은 그룹에 넣고 그룹 권한을 설정하면 해당 그룹에 속한 사람만 접근할 수 있습니다.

    chmod — 권한 변경하기

    chmod는 파일의 권한을 변경하는 명령어입니다. change mode의 줄임말입니다. 사용법은 크게 두 가지가 있습니다.

    첫 번째는 숫자로 설정하는 방법입니다. 이게 더 자주 쓰입니다.

    chmod 755 deploy.sh

    여기서 755는 세 자리 숫자인데, 각 자리가 소유자, 그룹, 기타 사용자의 권한을 나타냅니다. 각 숫자는 r, w, x 권한의 합산입니다. r은 4, w는 2, x는 1입니다.

    7은 4+2+1이니까 읽기, 쓰기, 실행 전부 가능합니다. 5는 4+0+1이니까 읽기와 실행만 가능합니다. 0이면 아무 권한이 없습니다.

    그래서 chmod 755 deploy.sh는 소유자에게 모든 권한을 주고, 그룹과 기타 사용자에게는 읽기와 실행 권한만 주겠다는 뜻입니다.

    자주 사용하는 숫자 조합을 정리하면 이렇습니다. 644는 소유자가 읽기 쓰기, 나머지는 읽기만 가능한 설정이고 일반 파일에 많이 씁니다. 755는 소유자가 전부 가능하고 나머지는 읽기 실행만 가능한 설정이고 스크립트나 디렉토리에 많이 씁니다. 600은 소유자만 읽기 쓰기 가능하고 나머지는 접근 불가인 설정이고 SSH 키 같은 민감한 파일에 씁니다. 700은 소유자만 전부 가능하고 나머지는 접근 불가인 설정이고 개인 디렉토리에 씁니다.

    두 번째는 문자로 설정하는 방법입니다.

    chmod u+x deploy.sh

    u는 소유자(user), g는 그룹(group), o는 기타(others), a는 전체(all)입니다. +는 권한 추가, -는 권한 제거입니다. 위 명령어는 소유자에게 실행 권한을 추가하라는 뜻입니다.

    특정 권한 하나만 바꾸고 싶을 때는 문자 방식이 편합니다. 전체 권한을 한 번에 설정할 때는 숫자 방식이 깔끔합니다.

    chown — 소유자 변경하기

    chown은 파일의 소유자와 그룹을 변경하는 명령어입니다. change owner의 줄임말입니다.

    sudo chown www-data:www-data /var/www/today-play/index.html

    콜론 앞이 소유자, 뒤가 그룹입니다. 이 명령어는 index.html 파일의 소유자와 그룹을 둘 다 www-data로 바꿉니다. 웹 서버가 이 파일을 읽어야 하는데 소유자가 다른 사용자로 되어 있으면 권한 문제가 생기기 때문에, 웹 서버 사용자로 소유권을 넘기는 겁니다.

    소유자만 바꾸고 싶으면 콜론과 그룹을 생략합니다.

    sudo chown www-data /var/www/today-play/index.html

    그룹만 바꾸고 싶으면 콜론 앞을 비우면 됩니다.

    sudo chown :developers /var/www/today-play/index.html

    디렉토리와 그 안의 모든 파일의 소유자를 한 번에 바꾸려면 -R 옵션을 사용합니다.

    sudo chown -R www-data:www-data /var/www/today-play/

    -R은 재귀적(recursive)으로 하위 파일과 폴더를 전부 포함한다는 뜻입니다. 웹 사이트 디렉토리 전체의 소유자를 바꿀 때 자주 씁니다. chmod에도 -R 옵션이 있고 같은 방식으로 동작합니다.

    chown은 보통 sudo를 붙여야 합니다. 파일 소유권을 바꾸는 건 시스템 관리자 권한이 필요한 작업이기 때문입니다.

    디렉토리 권한은 파일과 다르다

    디렉토리에서 r, w, x 권한의 의미는 파일과 조금 다릅니다.

    디렉토리에서 r은 디렉토리 안의 파일 목록을 볼 수 있는 권한입니다. ls 명령어로 내용을 확인할 수 있느냐의 문제입니다.

    w는 디렉토리 안에서 파일을 생성하거나 삭제할 수 있는 권한입니다. 파일 내용을 수정하는 것과는 별개입니다. 디렉토리에 w 권한이 있으면 그 안에 파일을 만들거나 지울 수 있습니다.

    x는 디렉토리에 접근할 수 있는 권한입니다. cd 명령어로 디렉토리 안에 들어가려면 x 권한이 필요합니다. 디렉토리에 r은 있는데 x가 없으면 파일 목록은 볼 수 있지만 그 파일에 실제로 접근하지는 못하는 어중간한 상태가 됩니다.

    그래서 디렉토리 권한은 보통 755로 설정합니다. 소유자는 전부 가능하고 다른 사용자는 목록 확인과 접근만 가능한 상태입니다.

    실무에서 자주 만나는 상황들

    웹 서버가 파일을 못 읽는 경우가 가장 흔합니다. Nginx가 www-data 사용자로 실행되는데, 웹 사이트 파일의 소유자가 ubuntu이고 권한이 700이면 www-data는 해당 파일에 접근할 수 없습니다. 이럴 때 파일의 소유자를 www-data로 바꾸거나, 기타 사용자에게 읽기 권한을 주면 해결됩니다.

    SSH 키 파일의 권한이 너무 열려 있는 경우도 많이 겪습니다. SSH 접속에 사용하는 개인 키 파일(.pem이나 id_rsa)은 소유자만 읽을 수 있어야 합니다. 권한이 644 이상으로 열려 있으면 SSH가 아예 접속을 거부합니다. “WARNING: UNPROTECTED PRIVATE KEY FILE!”이라는 경고가 나오면서 연결이 안 됩니다. 이때 chmod 600으로 설정하면 됩니다.

    chmod 600 ~/.ssh/id_rsa

    배포 후에 업로드된 파일이 안 보이는 경우도 있습니다. 사용자가 웹 사이트를 통해 이미지를 업로드했는데, 해당 디렉토리의 권한이 쓰기를 허용하지 않으면 파일 저장이 실패합니다. 업로드 디렉토리에는 웹 서버 사용자가 쓰기 가능하도록 설정해야 합니다.

    chmod 777은 왜 쓰면 안 되는가

    권한 문제가 생기면 인터넷에서 “chmod 777 해라”라는 답변을 쉽게 찾을 수 있습니다. 777은 모든 사용자에게 모든 권한을 주는 설정입니다. 당장은 문제가 해결되지만 보안 관점에서 매우 위험합니다.

    서버에 접속할 수 있는 누구라도 해당 파일을 읽고, 수정하고, 실행할 수 있게 됩니다. 만약 웹 서버에 취약점이 있어서 공격자가 시스템에 접근했을 때, 777로 설정된 파일은 마음대로 조작할 수 있습니다.

    권한 문제가 생기면 무조건 777로 열지 말고, 어떤 사용자가 어떤 권한이 필요한지를 먼저 파악해야 합니다. 대부분의 경우 소유자를 올바르게 설정하거나 그룹 권한을 조정하는 것만으로 해결됩니다.

    umask — 기본 권한을 결정하는 값

    파일을 새로 만들면 기본 권한이 자동으로 설정됩니다. 이 기본 권한을 결정하는 게 umask 값입니다.

    터미널에서 umask를 입력하면 현재 값을 확인할 수 있습니다.

    umask
    0022

    umask는 “이 권한은 빼겠다”라는 의미입니다. 파일의 최대 기본 권한은 666이고 디렉토리는 777입니다. 여기서 umask 값을 빼면 실제 기본 권한이 됩니다.

    umask가 0022이면 파일은 666에서 022를 뺀 644가 기본 권한이 됩니다. 디렉토리는 777에서 022를 뺀 755가 됩니다. 파일을 만들 때마다 매번 chmod를 하지 않아도 되는 이유가 이것입니다.

    보안을 강화하고 싶으면 umask를 0077로 설정하면 됩니다. 그러면 새로 만든 파일은 600, 디렉토리는 700이 되어서 소유자만 접근할 수 있습니다.

    마무리

    리눅스 파일 권한은 소유자, 그룹, 기타 사용자 세 범위에 대해 읽기, 쓰기, 실행 권한을 각각 설정하는 구조입니다. chmod로 권한을 바꾸고 chown으로 소유자를 바꿉니다. 처음에는 숫자 조합이 헷갈리지만 644, 755, 600 이 세 가지만 기억해도 대부분의 상황에 대응할 수 있습니다. 핵심은 필요한 만큼만 권한을 열고 나머지는 닫아두는 것입니다. 다음 글에서는 서버에서 반복 작업을 자동으로 실행해주는 크론탭(Crontab) 사용법을 다뤄보겠습니다.

  • Nginx 기본 설정 파일 구조 이해하기

    Nginx 기본 설정 파일 구조 이해하기

    Nginx를 설치하고 나면 가장 먼저 마주하는 게 설정 파일입니다. 브라우저에서 접속했을 때 어떤 파일을 보여줄지, 어떤 포트에서 요청을 받을지, SSL은 어떻게 처리할지 전부 이 설정 파일에서 결정됩니다. 그런데 처음 파일을 열어보면 중괄호가 겹겹이 들어가 있고 낯선 지시어들이 나열되어 있어서 어디를 어떻게 건드려야 하는지 감이 안 옵니다. 이번 글에서는 Nginx 설정 파일의 전체 구조를 잡아주고, 각 블록이 무슨 역할을 하는지 실제 파일을 보면서 정리해보겠습니다.

    설정 파일 위치부터 확인하기

    Ubuntu 기준으로 Nginx를 설치하면 설정 파일들이 /etc/nginx/ 디렉토리에 생깁니다. 터미널에서 이 디렉토리를 열어보면 여러 파일과 폴더가 보입니다.

    ls /etc/nginx/

    여기서 중요한 것들만 추리면 이렇습니다.

    nginx.conf가 메인 설정 파일입니다. Nginx가 실행될 때 가장 먼저 읽는 파일이고, 전체 동작의 뼈대가 여기에 정의되어 있습니다.

    sites-available 폴더에는 사이트별 설정 파일을 만들어둡니다. 예를 들어 today-play.com 설정 파일을 여기에 작성합니다.

    sites-enabled 폴더에는 실제로 활성화할 사이트의 심볼릭 링크를 넣습니다. sites-available에 파일을 만들었다고 바로 적용되는 게 아니라, sites-enabled에 링크를 걸어야 Nginx가 인식합니다.

    conf.d 폴더도 있는데, 이건 sites-available/sites-enabled 구조 대신 사용할 수 있는 대안입니다. 이 폴더에 .conf 확장자로 파일을 넣으면 자동으로 로드됩니다. 배포판에 따라 둘 중 하나만 사용하는 경우도 있습니다.

    mime.types 파일은 파일 확장자와 Content-Type을 매핑하는 파일입니다. .html 파일을 요청하면 text/html로 응답하고, .jpg 파일을 요청하면 image/jpeg로 응답하는 게 이 파일 덕분입니다. 보통 건드릴 일이 없습니다.

    nginx.conf 파일 열어보기

    핵심은 nginx.conf입니다. 이 파일을 열어보면 대략 이런 구조로 되어 있습니다.

    user www-data;
    worker_processes auto;
    pid /run/nginx.pid;
    
    events {
        worker_connections 1024;
    }
    
    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    
        sendfile on;
        keepalive_timeout 65;
    
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }

    처음 보면 복잡해 보이지만 크게 세 부분으로 나뉩니다. 최상위 컨텍스트, events 블록, http 블록입니다. Nginx 설정은 이 블록 구조를 이해하는 게 전부라고 해도 과언이 아닙니다.

    최상위 컨텍스트

    파일 맨 위에 있는 부분으로, 어떤 블록에도 속하지 않는 지시어들입니다.

    user www-data는 Nginx 워커 프로세스가 어떤 시스템 사용자 권한으로 실행될지 지정합니다. www-data는 Ubuntu에서 웹 서버용으로 기본 생성되는 사용자입니다. 보안상 root로 실행하면 안 되기 때문에 권한이 낮은 사용자를 지정하는 겁니다.

    worker_processes auto는 워커 프로세스 수를 설정합니다. auto로 두면 서버의 CPU 코어 수에 맞춰서 자동으로 설정됩니다. 코어가 4개면 워커 프로세스도 4개가 뜹니다. 이전 글에서 Nginx가 적은 수의 프로세스로 많은 요청을 처리한다고 했는데, 여기서 그 프로세스 수가 결정됩니다.

    pid /run/nginx.pid는 Nginx 마스터 프로세스의 PID를 저장할 파일 경로입니다. 시스템이 Nginx를 관리할 때 이 파일을 참조합니다. 건드릴 일이 거의 없습니다.

    events 블록

    events 블록은 Nginx의 연결 처리 방식을 설정하는 곳입니다.

    events {
        worker_connections 1024;
    }

    worker_connections 1024는 각 워커 프로세스가 동시에 처리할 수 있는 최대 연결 수입니다. 워커 프로세스가 4개이고 worker_connections가 1024면 이론상 동시 연결 4096개까지 처리할 수 있다는 뜻입니다. 트래픽이 많은 서비스라면 이 숫자를 늘려야 할 수도 있지만, 일반적인 사이트에서는 기본값으로 충분합니다.

    이 블록에 multi_accept on을 추가하면 워커 프로세스가 한 번에 여러 연결을 받아들일 수 있습니다. 기본값은 off인데, 트래픽이 많다면 켜는 게 좋습니다.

    http 블록

    http 블록이 가장 큰 블록이고, 웹 서버로서의 핵심 설정이 여기에 들어갑니다.

    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    
        sendfile on;
        keepalive_timeout 65;
    
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }

    include /etc/nginx/mime.types는 아까 말한 MIME 타입 매핑 파일을 불러오는 겁니다. include는 다른 파일의 내용을 그 자리에 삽입하는 지시어입니다.

    default_type application/octet-stream은 MIME 타입을 판별할 수 없는 파일의 기본 타입입니다. octet-stream으로 설정하면 브라우저가 해당 파일을 다운로드하게 됩니다.

    sendfile on은 파일 전송 시 커널에서 직접 처리하게 하는 옵션입니다. 유저 공간을 거치지 않고 커널 레벨에서 파일을 보내기 때문에 정적 파일 전송 성능이 좋아집니다.

    keepalive_timeout 65는 클라이언트와의 연결을 유지하는 시간입니다. 초 단위이고, 이 시간 동안 추가 요청이 없으면 연결을 닫습니다.

    맨 아래에 있는 두 줄의 include가 중요합니다. conf.d 폴더의 .conf 파일과 sites-enabled 폴더의 파일을 전부 불러옵니다. 즉 사이트별 설정 파일은 이 include를 통해 http 블록 안에 포함되는 구조입니다.

    server 블록 — 사이트별 설정

    server 블록은 http 블록 안에 들어가고, 각 사이트(또는 가상 호스트)의 설정을 담당합니다. 보통 sites-available 폴더에 별도 파일로 만듭니다.

    server {
        listen 80;
        server_name today-play.com www.today-play.com;
        root /var/www/today-play;
        index index.html;
    
        location / {
            try_files $uri $uri/ =404;
        }
    }

    listen 80은 이 서버 블록이 80번 포트에서 요청을 받겠다는 뜻입니다. HTTPS를 적용했다면 listen 443 ssl도 추가됩니다.

    server_name은 이 서버 블록이 어떤 도메인의 요청을 처리할지 지정합니다. 하나의 서버에 여러 도메인을 연결할 수 있는데, server_name이 다른 server 블록을 여러 개 만들면 됩니다. Nginx가 요청이 들어올 때 Host 헤더를 보고 어떤 server 블록으로 보낼지 결정합니다.

    root는 웹 사이트의 루트 디렉토리입니다. 사용자가 today-play.com/about.html을 요청하면 /var/www/today-play/about.html 파일을 찾아서 보내줍니다.

    index는 디렉토리에 접근했을 때 기본으로 보여줄 파일입니다. today-play.com으로 접속하면 /var/www/today-play/index.html을 보여줍니다.

    location 블록 — URL 경로별 처리

    location 블록은 server 블록 안에 들어가고, 특정 URL 경로에 대한 처리 방식을 정의합니다.

    location / {
        try_files $uri $uri/ =404;
    }

    이건 모든 경로에 대해 파일이 있으면 보여주고, 디렉토리면 디렉토리 안의 index 파일을 보여주고, 둘 다 아니면 404 에러를 반환하라는 뜻입니다.

    location 블록은 여러 개 만들 수 있고 경로별로 다른 동작을 지정할 수 있습니다.

    location /images/ {
        alias /var/www/static/images/;
    }
    
    location /api/ {
        proxy_pass http://localhost:3000;
    }

    첫 번째는 /images/ 경로로 들어오는 요청을 /var/www/static/images/ 디렉토리에서 찾으라는 설정입니다. 두 번째는 /api/로 시작하는 요청을 로컬의 3000번 포트에서 돌아가는 애플리케이션 서버로 넘기라는 설정입니다. 이게 리버스 프록시 설정입니다. Node.js나 Django 같은 백엔드 서버를 Nginx 뒤에 두고 싶을 때 이렇게 합니다.

    location 블록의 매칭 방식도 알아두면 좋습니다. 기본적으로 앞에서부터 일치하는 경로를 찾는데, 정확히 일치하는 경로를 지정하고 싶으면 등호를 사용합니다.

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    이건 정확히 /favicon.ico 요청에만 적용되고, 해당 파일이 없어도 로그를 남기지 않겠다는 설정입니다. 파비콘이 없을 때 로그가 계속 쌓이는 걸 방지할 수 있습니다.

    전체 구조를 한눈에 보면

    Nginx 설정 파일은 블록이 중첩되는 구조입니다. 정리하면 이렇습니다.

    nginx.conf 파일 안에 최상위 지시어들이 있고, 그 안에 events 블록이 있고, http 블록이 있습니다. http 블록 안에 server 블록이 들어가고, server 블록 안에 location 블록이 들어갑니다. 바깥쪽 블록에서 설정한 값은 안쪽 블록으로 상속됩니다. 안쪽에서 같은 지시어를 다시 설정하면 덮어씌워집니다.

    nginx.conf
    ├── 최상위 (user, worker_processes, pid)
    ├── events { ... }
    └── http { ... }
        ├── 공통 설정 (mime.types, sendfile, keepalive)
        ├── include conf.d/*.conf
        └── include sites-enabled/*
            └── server { ... }
                ├── listen, server_name, root
                └── location / { ... }
                └── location /api/ { ... }

    실무에서 nginx.conf 자체를 직접 많이 수정하는 경우는 드뭅니다. 대부분 sites-available에 사이트별 설정 파일을 만들고, sites-enabled에 링크를 걸어서 관리합니다. nginx.conf에는 전체 서버에 공통으로 적용되는 설정만 두는 게 깔끔합니다.

    설정 파일 수정 후 확인하는 방법

    설정 파일을 수정했으면 바로 리로드하기 전에 문법 오류가 없는지 확인하는 게 좋습니다.

    sudo nginx -t

    이 명령어를 실행하면 설정 파일 문법을 검사합니다. “syntax is ok”와 “test is successful”이 나오면 문제가 없는 겁니다. 에러가 있으면 어느 파일 몇 번째 줄에서 문제가 생겼는지 알려줍니다.

    문법 확인이 끝났으면 설정을 반영합니다.

    sudo nginx -s reload

    reload는 실행 중인 Nginx를 중단하지 않고 설정만 다시 읽어들입니다. 사이트가 운영 중이어도 안전하게 적용할 수 있습니다. restart와 다른 점은, reload는 기존 연결을 끊지 않고 새 설정을 적용한다는 겁니다.

    설정할 때 자주 실수하는 것들

    가장 흔한 실수는 세미콜론을 빠뜨리는 겁니다. Nginx 설정에서 각 지시어는 반드시 세미콜론으로 끝나야 합니다. 빠뜨리면 nginx -t에서 바로 에러가 나는데, 에러 메시지가 가리키는 줄이 실제 실수한 줄과 다를 수 있어서 헷갈릴 수 있습니다.

    두 번째는 sites-available에 파일만 만들고 sites-enabled에 링크를 안 거는 경우입니다. 심볼릭 링크를 만드는 명령어는 이렇습니다.

    sudo ln -s /etc/nginx/sites-available/today-play.com /etc/nginx/sites-enabled/

    세 번째는 default 설정 파일과 충돌하는 경우입니다. sites-enabled에 default 파일이 남아있으면 내 사이트 설정과 충돌할 수 있습니다. 내 사이트 설정이 제대로 안 먹힌다면 default 링크를 삭제하고 다시 reload해보면 해결되는 경우가 많습니다.

    마무리

    Nginx 설정 파일은 블록 구조만 이해하면 절반은 끝난 겁니다. nginx.conf가 전체 뼈대를 잡고, server 블록이 사이트를 정의하고, location 블록이 경로별 동작을 처리합니다. 처음에는 설정 파일이 낯설지만 실제로 건드리는 부분은 server와 location 블록이 대부분이라 몇 번 해보면 금방 익숙해집니다. 다음 글에서는 리눅스 서버에서 자주 다루게 되는 파일 권한 설정에 대해 다뤄보겠습니다.

  • Nginx와 Apache 차이 및 선택 기준

    Nginx와 Apache 차이 및 선택 기준

    웹 서버를 설치하려고 검색하면 거의 반드시 Nginx와 Apache 두 이름이 나옵니다. 둘 다 웹 서버 소프트웨어이고 둘 다 무료이고 둘 다 전 세계적으로 엄청나게 많이 사용됩니다. 그러다 보니 처음 서버를 세팅하는 입장에서는 도대체 뭘 써야 하는지 헷갈릴 수밖에 없습니다. 이번 글에서는 Apache와 Nginx가 각각 어떤 방식으로 동작하는지, 어떤 상황에서 어떤 걸 선택하면 좋은지를 정리해보겠습니다.

    Apache — 오래된 만큼 안정적인 웹 서버

    Apache HTTP Server는 1995년에 처음 나왔습니다. 웹이 본격적으로 퍼지던 시기에 등장해서 오랫동안 웹 서버 점유율 1위를 유지했습니다. 지금도 전체 웹 서버 중 상당한 비율을 차지하고 있고, 특히 워드프레스 같은 PHP 기반 서비스에서 많이 사용됩니다.

    Apache의 가장 큰 특징은 요청 하나당 프로세스 또는 스레드를 할당하는 방식으로 동작한다는 점입니다. 사용자가 접속하면 그 요청을 처리할 프로세스가 하나 생기고, 처리가 끝나면 반환됩니다. 이 방식을 MPM(Multi-Processing Module)이라고 하는데, prefork, worker, event 세 가지 모드가 있습니다. 기본값인 prefork 모드는 프로세스 하나가 요청 하나를 담당합니다. 직관적이고 안정적이지만 동시 접속이 많아지면 프로세스 수가 급격히 늘어나면서 메모리를 많이 잡아먹는다는 단점이 있습니다.

    Apache의 또 다른 특징은 .htaccess 파일입니다. 디렉토리별로 .htaccess 파일을 두면 서버 전체 설정을 건드리지 않고 해당 디렉토리의 동작만 바꿀 수 있습니다. URL 리다이렉트, 접근 제한, 인증 설정 같은 걸 디렉토리 단위로 제어할 수 있어서 공유 호스팅 환경에서 특히 유용합니다. 다만 Apache가 요청마다 .htaccess 파일을 읽어야 하기 때문에 성능에는 약간의 부담이 됩니다.

    모듈 시스템도 강점입니다. mod_rewrite, mod_security, mod_php 같은 모듈을 동적으로 로드할 수 있어서 기능 확장이 유연합니다. PHP를 모듈 형태로 바로 붙일 수 있다는 점 때문에 PHP 기반 프로젝트에서는 Apache가 여전히 편리합니다.

    Nginx — 대량 트래픽에 강한 웹 서버

    Nginx는 2004년에 러시아 개발자 이고르 시쇼프가 만들었습니다. 당시 Apache가 동시 접속 1만 개를 넘기면 성능이 급격히 떨어지는 문제가 있었는데, 이걸 해결하려는 목적으로 개발된 겁니다. 이른바 C10K 문제라고 불리던 것입니다.

    Nginx는 Apache와 근본적으로 다른 구조를 가지고 있습니다. 이벤트 기반 비동기 방식으로 동작합니다. 요청마다 프로세스를 만드는 게 아니라, 하나의 워커 프로세스가 수천 개의 연결을 동시에 처리합니다. 새 요청이 들어오면 이벤트 큐에 등록하고, 처리할 준비가 되면 처리하는 식입니다. 이 방식 덕분에 동시 접속이 많아져도 메모리 사용량이 크게 늘지 않습니다.

    정적 파일 처리 속도도 Nginx가 빠릅니다. HTML, CSS, JavaScript, 이미지 같은 정적 파일을 서빙할 때 Apache보다 눈에 띄게 좋은 성능을 보여줍니다. 그래서 정적 파일이 많은 사이트에서는 Nginx가 확실히 유리합니다.

    Nginx는 웹 서버 역할만 하는 게 아니라 리버스 프록시, 로드 밸런서 역할도 합니다. 앞단에 Nginx를 두고 뒤에 Node.js, Django, Spring 같은 애플리케이션 서버를 배치하는 구성이 매우 흔합니다. 이런 구조에서 Nginx는 클라이언트 요청을 받아서 뒤쪽 서버로 전달하고 응답을 돌려주는 역할을 합니다. 실무에서 Nginx를 리버스 프록시 용도로 쓰는 경우가 상당히 많습니다.

    다만 Nginx는 .htaccess 같은 디렉토리별 설정 기능이 없습니다. 모든 설정을 메인 설정 파일에서 관리해야 합니다. 설정을 바꾸면 Nginx를 리로드해야 반영되기 때문에 공유 호스팅 환경에서는 불편할 수 있습니다. 하지만 직접 서버를 관리하는 환경에서는 오히려 설정이 한 곳에 모여 있어서 관리하기 편하다는 의견도 많습니다.

    동작 방식 비교

    Apache와 Nginx의 차이를 가장 쉽게 이해하는 방법은 식당에 비유하는 겁니다.

    Apache의 prefork 방식은 손님이 올 때마다 전담 웨이터를 한 명씩 붙여주는 식당입니다. 손님이 10명이면 웨이터 10명, 100명이면 웨이터 100명이 필요합니다. 손님 하나하나한테 세심하게 대응할 수 있지만, 손님이 갑자기 몰리면 웨이터가 부족해지고 식당이 비좁아집니다.

    Nginx는 웨이터 한두 명이 홀 전체를 담당하는 식당입니다. 주문을 받아놓고 음식이 준비되면 가져다주고, 그 사이에 다른 테이블 주문도 받습니다. 손님이 100명이든 1000명이든 웨이터 수는 크게 늘지 않습니다. 다만 요리가 오래 걸리는 복잡한 주문이 많으면 그건 주방(백엔드 서버)에서 처리해야 하고, Nginx는 서빙에 집중합니다.

    이 차이가 실제 성능에 그대로 반영됩니다. 동시 접속이 적을 때는 둘 다 큰 차이가 없지만, 접속이 수천 단위로 올라가면 Nginx가 훨씬 적은 리소스로 버틸 수 있습니다.

    PHP 처리 방식의 차이

    PHP를 사용하는 경우 둘의 차이가 좀 더 명확해집니다.

    Apache는 mod_php 모듈을 통해 PHP를 자체적으로 처리할 수 있습니다. Apache 프로세스 안에서 PHP가 바로 실행되기 때문에 별도의 설정 없이 PHP 파일을 요청하면 바로 처리됩니다. 워드프레스를 Apache에서 돌리는 게 간편한 이유가 이것입니다.

    Nginx는 PHP를 직접 처리하지 못합니다. 대신 PHP-FPM이라는 별도의 프로세스 매니저를 통해 처리합니다. Nginx가 PHP 요청을 받으면 PHP-FPM에게 넘기고, 결과를 받아서 사용자에게 돌려주는 구조입니다. 초기 설정이 Apache보다 한 단계 더 필요하지만, PHP-FPM이 프로세스를 효율적으로 관리하기 때문에 트래픽이 많을 때는 오히려 이 구조가 더 안정적입니다.

    설정 파일 구조 비교

    Apache의 설정은 보통 /etc/apache2/ 디렉토리에 있습니다. apache2.conf가 메인 설정 파일이고, sites-available 폴더에 가상 호스트 설정을 만들어서 sites-enabled에 심볼릭 링크로 연결하는 방식입니다. 여기에 디렉토리별 .htaccess까지 있으니 설정이 여러 곳에 분산되어 있는 셈입니다.

    Nginx의 설정은 /etc/nginx/ 디렉토리에 있습니다. nginx.conf가 메인 파일이고, 마찬가지로 sites-available과 sites-enabled 구조를 사용합니다. 다만 .htaccess가 없기 때문에 모든 설정이 이 파일들 안에 다 들어갑니다. 설정 문법도 Apache와 다릅니다. Apache는 XML 비슷한 문법을 쓰고, Nginx는 중괄호 기반의 자체 문법을 사용합니다.

    설정을 바꾼 뒤에도 차이가 있습니다. Apache는 graceful restart로 무중단 반영이 가능하고, Nginx는 nginx -s reload 명령어로 설정을 리로드합니다. 둘 다 서비스 중단 없이 설정을 반영할 수 있습니다.

    어떤 상황에서 뭘 선택할까

    워드프레스나 PHP 기반 사이트를 공유 호스팅 환경에서 운영한다면 Apache가 편합니다. .htaccess를 통해 디렉토리별 설정이 가능하고 PHP 모듈을 바로 붙일 수 있기 때문입니다. 대부분의 웹 호스팅 업체가 Apache를 기본으로 제공하는 이유이기도 합니다.

    Node.js, Python, Java 같은 백엔드 애플리케이션 앞에 웹 서버를 둘 거라면 Nginx가 적합합니다. 리버스 프록시 설정이 간단하고 정적 파일 처리 성능이 좋기 때문입니다. 요즘 배포 구조에서 가장 흔하게 보이는 조합이 Nginx + 백엔드 서버 구성입니다.

    트래픽이 많거나 동시 접속이 중요한 서비스라면 Nginx가 유리합니다. 메모리 효율이 좋고 동시 처리 성능이 높기 때문입니다.

    사실 최근 추세를 보면 신규 프로젝트에서는 Nginx를 선택하는 경우가 더 많습니다. 클라우드 환경과 컨테이너 기반 배포가 늘면서 가볍고 빠른 웹 서버가 선호되기 때문입니다. 하지만 Apache가 나쁜 선택이라는 뜻은 아닙니다. 이미 Apache로 잘 돌아가고 있는 서비스를 굳이 Nginx로 바꿀 이유는 없습니다.

    둘 다 쓰는 경우도 있다

    재미있는 건 Apache와 Nginx를 같이 쓰는 구성도 있다는 겁니다. 앞단에 Nginx를 리버스 프록시로 두고 뒤에 Apache를 배치하는 방식입니다. Nginx가 정적 파일 처리와 SSL 종료를 담당하고, 동적 요청은 Apache로 넘기는 구조입니다. 이렇게 하면 Nginx의 성능과 Apache의 모듈 호환성을 둘 다 활용할 수 있습니다. 대규모 서비스에서 가끔 이런 구성을 사용합니다.

    마무리

    정리하면 Apache는 오랜 역사만큼 안정적이고 PHP와의 궁합이 좋으며 디렉토리별 설정이 유연합니다. Nginx는 동시 접속 처리에 강하고 리버스 프록시 기능이 뛰어나며 메모리 효율이 좋습니다. 처음 서버를 세팅하는 거라면 자기 프로젝트가 어떤 기술 스택을 쓰는지를 먼저 확인하고, 거기에 맞는 웹 서버를 선택하면 됩니다. 다음 글에서는 Nginx를 선택했을 때 처음 마주하게 되는 설정 파일 구조를 자세히 다루겠습니다.

  • 무료 SSL 인증서 적용하기 — Let’s Encrypt 사용법

    무료 SSL 인증서 적용하기 — Let’s Encrypt 사용법

    사이트에 도메인까지 연결했으면 그다음으로 해야 할 일이 SSL 인증서 적용입니다. SSL이 없으면 브라우저 주소창에 “주의 요함”이라는 경고가 뜨고, 사용자 입장에서는 이 사이트가 안전하지 않다는 인상을 받게 됩니다. 구글 검색 순위에도 영향을 주기 때문에 사실상 선택이 아니라 필수입니다. 예전에는 SSL 인증서가 유료였는데 지금은 Let’s Encrypt 덕분에 무료로 적용할 수 있습니다. 이번 글에서는 Let’s Encrypt가 무엇인지, 그리고 실제 서버에 어떻게 적용하는지를 정리해보겠습니다.

    SSL 인증서가 필요한 이유

    SSL 인증서가 하는 일은 간단합니다. 사용자의 브라우저와 서버 사이에 오가는 데이터를 암호화하는 겁니다. 로그인할 때 비밀번호를 입력하면 그 데이터가 네트워크를 타고 서버까지 갑니다. SSL이 없으면 이 데이터가 평문 그대로 전송되기 때문에 중간에 누군가 가로채면 그대로 노출됩니다. SSL이 적용되면 이 데이터가 암호화되어서 가로채더라도 내용을 알 수 없습니다.

    눈에 보이는 차이도 있습니다. SSL이 적용된 사이트는 주소가 https로 시작하고 주소창에 자물쇠 아이콘이 표시됩니다. 반대로 SSL이 없으면 http로 시작하고 브라우저가 경고를 띄웁니다. 특히 크롬은 꽤 눈에 띄게 경고를 보여주기 때문에 방문자가 바로 뒤로가기를 누를 수도 있습니다.

    Let’s Encrypt란 무엇인가

    Let’s Encrypt는 비영리 기관인 ISRG(Internet Security Research Group)에서 운영하는 무료 SSL 인증서 발급 서비스입니다. 2016년에 정식 출시됐고 지금은 전 세계 수억 개 사이트에서 사용하고 있습니다.

    유료 인증서와 암호화 수준 차이는 없습니다. 다만 유효기간이 90일이라서 주기적으로 갱신해야 한다는 차이가 있습니다. 이것도 자동 갱신 설정을 해두면 신경 쓸 일이 없습니다.

    Let’s Encrypt 인증서를 발급받으려면 Certbot이라는 도구를 사용합니다. Certbot은 Let’s Encrypt에서 공식으로 권장하는 클라이언트 프로그램이고, 명령어 몇 줄이면 인증서 발급부터 웹 서버 설정까지 자동으로 처리해줍니다.

    사전 준비 사항

    Certbot을 실행하기 전에 확인해야 할 것들이 있습니다.

    첫째, 도메인이 서버 IP에 정상적으로 연결되어 있어야 합니다. Let’s Encrypt는 인증서를 발급하기 전에 “이 도메인이 정말 이 서버의 것이 맞는지” 검증하는 과정을 거칩니다. 도메인으로 접속했을 때 서버에 도달할 수 있어야 검증이 통과됩니다. 이전 글에서 다룬 A레코드 설정이 완료되어 있어야 한다는 뜻입니다.

    둘째, 서버에 웹 서버가 설치되어 있고 80번 포트가 열려 있어야 합니다. Certbot이 검증할 때 80번 포트를 사용하기 때문입니다. Nginx든 Apache든 하나는 설치되어 있어야 합니다.

    셋째, 서버에 SSH로 접속할 수 있어야 합니다. Certbot은 서버 내부에서 직접 실행하는 도구이기 때문입니다.

    Certbot 설치 방법

    Ubuntu 서버를 기준으로 설명하겠습니다. 대부분의 클라우드 서버가 Ubuntu를 사용하니까 거의 그대로 따라 하실 수 있을 겁니다.

    먼저 서버에 SSH로 접속한 다음 Certbot과 Nginx 플러그인을 설치합니다.

    sudo apt update
    sudo apt install certbot python3-certbot-nginx

    Apache를 사용하고 있다면 python3-certbot-nginx 대신 python3-certbot-apache를 설치하면 됩니다. 이 글에서는 Nginx 기준으로 진행합니다.

    설치가 끝나면 certbot 명령어가 사용 가능한 상태가 됩니다.

    인증서 발급 실행

    설치가 끝났으면 다음 명령어 하나로 인증서 발급과 Nginx 설정을 동시에 처리할 수 있습니다.

    sudo certbot --nginx -d today-play.com -d www.today-play.com

    여기서 -d 옵션 뒤에 오는 게 인증서를 적용할 도메인입니다. 루트 도메인과 www 도메인을 같이 넣어주는 게 좋습니다. 둘 다 넣어야 어떤 주소로 접속하든 https가 적용됩니다.

    처음 실행하면 이메일 주소를 입력하라고 나옵니다. 인증서 만료 알림을 받을 주소를 입력하면 됩니다. 그다음 이용약관 동의 여부를 물어보고, HTTP 접속을 HTTPS로 자동 리다이렉트할지 물어봅니다. 리다이렉트는 설정하는 게 좋습니다. 사용자가 http로 접속해도 자동으로 https로 넘어가게 해줍니다.

    모든 과정이 끝나면 “Congratulations!”라는 메시지가 나오고 인증서가 적용됩니다. 브라우저에서 https://todayfunplay.com으로 접속해서 자물쇠 아이콘이 뜨는지 확인하면 됩니다.

    Certbot이 실제로 하는 일

    명령어 한 줄이면 끝나기 때문에 내부에서 뭘 하는 건지 궁금할 수 있습니다. Certbot은 크게 세 가지를 처리합니다.

    먼저 도메인 소유권 검증을 합니다. Let’s Encrypt 서버가 내 도메인으로 HTTP 요청을 보내서 Certbot이 미리 만들어둔 임시 파일에 접근할 수 있는지 확인합니다. 접근이 되면 “이 서버가 이 도메인을 가지고 있다”고 판단합니다.

    검증이 통과되면 인증서 파일을 발급받아서 서버에 저장합니다. 보통 /etc/letsencrypt/live/도메인명/ 경로에 저장됩니다.

    마지막으로 Nginx 설정 파일을 수정해서 SSL 관련 설정을 자동으로 추가합니다. 인증서 파일 경로, SSL 프로토콜 버전, 리다이렉트 규칙 같은 것들을 알아서 넣어줍니다.

    자동 갱신 설정

    Let’s Encrypt 인증서는 90일마다 만료됩니다. 만료되면 다시 “주의 요함” 경고가 뜨기 때문에 갱신을 까먹으면 안 됩니다.

    다행히 Certbot을 설치하면 자동 갱신 타이머가 같이 설정되는 경우가 많습니다. 확인하려면 이렇게 입력합니다.

    sudo systemctl status certbot.timer

    active 상태로 나오면 자동 갱신이 이미 동작하고 있는 겁니다. 만약 비활성화되어 있다면 직접 크론탭에 등록할 수 있습니다.

    sudo crontab -e

    파일이 열리면 맨 아래에 다음 줄을 추가합니다.

    0 3 * * * certbot renew --quiet

    이렇게 하면 매일 새벽 3시에 인증서 만료 여부를 확인하고, 만료가 가까우면 자동으로 갱신합니다. –quiet 옵션은 갱신할 게 없을 때 아무 출력도 하지 않게 하는 옵션입니다.

    갱신이 제대로 되는지 미리 테스트해보고 싶으면 다음 명령어를 사용합니다.

    sudo certbot renew --dry-run

    dry-run은 실제로 갱신하지 않고 과정만 시뮬레이션하는 겁니다. 여기서 에러가 나지 않으면 실제 갱신도 문제없이 될 겁니다.

    적용 후 확인하는 방법

    인증서를 적용했으면 제대로 동작하는지 확인해야 합니다. 가장 간단한 방법은 브라우저에서 직접 접속해보는 겁니다. 주소창에 자물쇠 아이콘이 뜨고 인증서 정보를 클릭했을 때 Let’s Encrypt가 발급한 인증서로 나오면 정상입니다.

    터미널에서 확인하고 싶으면 이렇게 입력합니다.

    curl -I https://todayfunplay.com

    응답 헤더에 HTTP/2 200이 나오면 SSL이 정상 동작하고 있는 겁니다. 만약 연결이 안 되면 443번 포트가 열려 있는지 먼저 확인해보세요.

    좀 더 자세한 인증서 상태를 보고 싶으면 SSL Labs(ssllabs.com/ssltest)에서 도메인을 입력하면 인증서 등급부터 보안 설정까지 상세하게 분석해줍니다.

    자주 겪는 문제와 해결법

    Certbot 실행 중에 에러가 나는 경우가 종종 있습니다.

    가장 흔한 건 “Could not automatically find a matching server block”이라는 에러입니다. 이건 Nginx 설정 파일에 server_name이 도메인과 일치하지 않을 때 나옵니다. /etc/nginx/sites-available/default 파일을 열어서 server_name 부분에 자기 도메인이 들어가 있는지 확인하면 됩니다.

    두 번째로 흔한 건 검증 실패입니다. “Challenge failed” 같은 메시지가 나오면 도메인이 서버 IP를 제대로 가리키고 있는지, 80번 포트가 열려 있는지, Nginx가 실행 중인지를 확인해야 합니다. DNS 설정을 방금 변경했다면 아직 전파가 안 됐을 수도 있으니 좀 기다렸다가 다시 시도해보는 것도 방법입니다.

    세 번째는 “Too many requests” 에러입니다. Let’s Encrypt는 같은 도메인에 대해 일주일에 발급할 수 있는 인증서 수에 제한이 있습니다. 테스트를 반복하다가 이 제한에 걸리면 일주일 정도 기다려야 합니다. 그래서 테스트할 때는 –staging 옵션을 붙여서 테스트용 인증서로 먼저 확인하는 게 좋습니다.

    마무리

    Let’s Encrypt와 Certbot 덕분에 SSL 인증서 적용이 정말 쉬워졌습니다. 서버에 접속해서 명령어 두세 줄이면 끝나고, 자동 갱신까지 설정해두면 그 이후로는 신경 쓸 일이 거의 없습니다. 도메인 연결까지 끝낸 상태라면 바로 적용해보시길 권합니다. 다음 글에서는 웹 서버를 선택할 때 많이 비교하게 되는 Nginx와 Apache의 차이를 정리해보겠습니다.

  • 서버에 도메인을 연결하는 방법 (A레코드, CNAME 설정)

    서버에 도메인을 연결하는 방법 — A레코드와 CNAME 설정

    서버를 하나 구했다고 해서 바로 사이트가 열리는 건 아닙니다. 브라우저에 IP 주소를 직접 치면 접속이 되긴 하지만, 사용자에게 “143.198.241.73으로 접속하세요”라고 안내할 수는 없잖아요. 그래서 도메인을 연결하는 과정이 필요합니다. 이번 글에서는 도메인을 서버에 연결할 때 반드시 알아야 하는 A레코드와 CNAME 설정 방법을 실제 과정 위주로 정리해보겠습니다.

    도메인과 서버는 원래 남남이다

    도메인을 구매했다고 해서 자동으로 내 서버와 연결되지 않습니다. 도메인은 가비아, Cloudflare, Namecheap 같은 도메인 등록업체에서 사고, 서버는 AWS, Vultr, Oracle Cloud 같은 호스팅 업체에서 따로 만들죠. 이 둘은 완전히 별개의 서비스입니다.

    둘을 이어주는 역할을 하는 게 DNS 설정입니다. DNS 관리 페이지에 들어가서 “이 도메인으로 들어오면 이 IP로 보내줘”라고 등록하는 겁니다. 이때 사용하는 게 바로 A레코드와 CNAME입니다.

    A레코드 — 도메인을 IP 주소에 직접 연결

    A레코드는 가장 기본적인 DNS 레코드입니다. 도메인 이름을 IPv4 주소에 직접 매핑합니다.

    예를 들어 내 서버의 IP가 143.198.241.73이고, 도메인이 today-play.com이라면 이렇게 설정합니다.

    타입호스트TTL
    A@143.198.241.733600

    호스트에 @를 넣으면 도메인 자체(today-play.com)를 의미합니다. 이렇게 설정하면 누군가 브라우저에 today-play.com을 입력했을 때, DNS 서버가 143.198.241.73을 알려주고, 브라우저는 그 IP로 접속하게 됩니다.

    처음 서버에 도메인을 연결할 때는 거의 대부분 A레코드를 사용합니다. 서버 IP가 고정되어 있다면 A레코드만으로 충분합니다.

    CNAME — 도메인을 다른 도메인에 연결

    CNAME은 IP가 아니라 다른 도메인 이름을 가리킵니다. 쉽게 말해서 “별명”을 만드는 겁니다.

    가장 흔하게 쓰는 경우가 www 처리입니다.

    타입호스트TTL
    CNAMEwwwtoday-play.com3600

    이렇게 설정하면 www.today-play.com으로 접속한 사람도 today-play.com과 같은 곳으로 연결됩니다. CNAME이 today-play.com을 가리키고, today-play.com의 A레코드가 서버 IP를 가리키는 구조입니다.

    CNAME은 Vercel, Netlify, GitHub Pages 같은 플랫폼에서도 자주 사용됩니다. 이런 서비스들은 IP가 수시로 바뀔 수 있어서 IP 대신 자체 도메인 주소를 안내해줍니다. 예를 들어 Vercel에서 커스텀 도메인을 연결하면 cname.vercel-dns.com을 CNAME으로 등록하라고 안내하는 식입니다.

    A레코드와 CNAME, 뭘 써야 할까?

    결론부터 말하면 상황에 따라 다릅니다.

    직접 서버를 운영하고 있고 IP가 고정되어 있다면 A레코드를 씁니다. 반면에 외부 호스팅 서비스에 연결하는 경우, 그 서비스에서 CNAME을 쓰라고 안내하면 CNAME을 씁니다.

    주의할 점이 하나 있는데, 루트 도메인(@)에는 CNAME을 설정하지 못하는 DNS 업체가 많습니다. 이건 DNS 표준 때문인데, CNAME은 해당 이름에 다른 레코드가 있으면 안 되거든요. 루트 도메인에는 보통 SOA, NS 레코드가 이미 있어서 충돌이 납니다. 그래서 Cloudflare 같은 일부 업체는 “CNAME Flattening”이라는 기술로 이 문제를 우회하기도 합니다.

    실제 설정 과정 예시

    가비아에서 도메인을 구매하고 AWS EC2 서버에 연결하는 과정을 간단히 정리하면 이렇습니다.

    먼저 AWS EC2 인스턴스에서 탄력적 IP(Elastic IP)를 할당받습니다. EC2를 중지했다 켜면 IP가 바뀌기 때문에, 고정 IP를 먼저 확보해야 합니다. 그다음 가비아 DNS 관리 페이지에 들어가서 A레코드에 호스트 @, 값에 할당받은 IP를 입력합니다. www도 처리하고 싶으면 CNAME 레코드에 호스트 www, 값에 내 도메인을 입력합니다.

    설정을 저장하고 나면 보통 수 분에서 최대 48시간 정도 걸릴 수 있다고 나오는데, 실제로는 대부분 10분 안에 반영됩니다. TTL 값이 낮을수록 빨리 퍼집니다.

    반영 여부를 확인하고 싶으면 터미널에서 이렇게 입력하면 됩니다.

    nslookup today-play.com

    여기서 내 서버 IP가 나오면 연결이 정상적으로 된 겁니다. 만약 예전 IP가 나오거나 응답이 없으면 아직 전파가 안 된 것이니 조금 기다리면 됩니다.

    설정할 때 자주 하는 실수들

    처음 도메인 연결할 때 은근히 삽질하는 부분이 있습니다.

    첫 번째는 IP를 잘못 입력하는 경우입니다. 특히 AWS를 쓸 때 퍼블릭 IP와 프라이빗 IP를 헷갈려서 프라이빗 IP를 입력하는 경우가 있습니다. DNS에는 반드시 퍼블릭 IP를 넣어야 합니다.

    두 번째는 서버에서 포트를 안 열어놓은 경우입니다. DNS 연결은 됐는데 사이트가 안 뜬다면 서버의 보안 그룹(AWS 기준)에서 80번, 443번 포트가 열려 있는지 확인해야 합니다.

    세 번째는 도메인 등록업체와 DNS 관리 위치가 다른 경우입니다. 도메인은 가비아에서 샀는데 네임서버를 Cloudflare로 변경했다면, DNS 레코드 설정은 Cloudflare에서 해야 합니다. 가비아에서 아무리 수정해도 반영이 안 됩니다.

    마무리

    도메인 연결은 서버 운영의 첫 관문 같은 단계입니다. A레코드로 IP를 직접 지정하고, 필요하면 CNAME으로 www나 외부 서비스를 연결하면 됩니다. 개념 자체는 단순한데 처음에 DNS 관리 화면을 보면 뭐가 뭔지 헷갈리는 게 정상입니다. 한 번 직접 해보면 그다음부터는 금방 합니다.

    다음 글에서는 도메인 연결 후에 거의 반드시 해야 하는 SSL 인증서 적용 방법(Let’s Encrypt)을 다루겠습니다.