Cấu hình Apache, Nginx và HAProxy chạy trên cùng Server (Debian, Ubuntu, CentOS)

Nếu bàn là một admin server và bạn có một web server mà bạn chọn như Apache hoặc Nginx. Apache là một web server rất nổi tiếng có từ những năm 1990s. Còn Nginx được phát triển sau này vào năm 2004, tuy nhiên Nginx phát triển rất nhanh và trở thành một web server rất mạnh mẽ, tốc độ xử lý rất nhanh cho những file html, và những file tỉnh khác.

Cả 2 Apache và Nginx đề hỗ trợ virtua hosting, có nghĩa là bạn có thể host rất nhiều website hoặc những ứng dụng web trên cùng một máy chủ. Tuy nhiên, bạn gặp phải tình huống là bạn có một website đang chạy trên web server này nhưng một ứng dụng web khác lại đòi hỏi phải chạy trên loại web server khác trên cùng một máy chủ. Port 80 và 443 đại điện trên địa chỉ IP Public và chỉ có thể được sử dụng cho một process. Nếu Apache đang sử dụng port này thì Nginx không thể sử dụng được nữa. Vì thể chúng ta phải làm gì trong tình huống này?

Bạn có thể cấu hình Nginx như là một reverse proxy cho Apache, Nginx có thể redirect nhưng request HTTP tới Apache. Theo kinh nghiệm của mình, tôi thấy rằng đây không phải lúc nào cũng là cách tốt nhất vì nó đã từng gây ra các vấn đề kỳ lạ mà tôi không thể khắc phục. Thay vì đó tôi sẽ sử dụng HAProxy để làm reverse proxy cho cả hai Apache và Nginx. HAProxy miễn phí, Open source là một Load Balancer có độ sẵn sàng cao và  là một proxy server cho những ứng dụng TCP và những ứng dụng trên nền HTTP.

Cấu hình Apache, Nginx và HAProxy trên cùng máy chủ

Bên dưới là một số thông tin chuẩn bị để cấu hình.

  • Nginx lắng nghe ở 127.0.0.1:80 và 127.0.0.1:443
  • Apache lắng nghe 127.0.0.2:80 và 127.0.0.2:443
  • HAProxy lắng nghe port 80 và 443 trên địa chỉ IP Public. Nginx chuyển hướng kết nối HTTP ở port 80 thành port 443. Khi một kết nối đến port 443, nó sẽ chọn giữa Nginx và Apache dựa vào SNI (Server Name Indication) header trong kết nối HTTPS.

Thực tế, Cloudflare nhà cung cấp dịch vụ CDN cũng sử dụng SNI header để xác định làm sao route kết nối HTTPS tới máy chủ web.

Bước 1: Tạm dừng dịch vụ Nginx và Apache

Để dừng dịch vụ Nginx trên Debian, Ubuntu và CentOS, chạy lệnh như bên dưới.

sudo systemctl stop nginx

Đê dừng dịch vụ Apache trên Debian/Ubuntu, chạy lện bên dưới.

sudo systemctl stop apache2

Để dừng dịch vụ Apache trên CentOS, chạy lệnh.

sudo systemctl stop httpd

Bước 2: Thay đổi port lắng nghe trên Nginx

Chúng ta cần thay đổi port Nginx lắng nghe ở địa chỉ 127.0.0.1:80. Mở file cấu hình Nginx ở thư mục /etc/nginx/conf.d/ hoặc /etc/nginx/sites-enabled/

listen 80;

Thay đổi thành

listen 127.0.0.1:80;

Nếu port https (443) được mở trên Nginx, bạn cũng phải thay đổi như bên dưới, tìm:

listen 443 ssl;

Thay đổi thành

listen 127.0.0.1:443 ssl;

File cấu hình chính của Nginx/etc/nginx/nginx.conf bao gồm virtual host mặt định lắng nghe ở port 80 và 443, vì thế bạn cần phải chỉnh sửa file này.

Khởi động Nginx để thay đổi cấu hình.

sudo systemctl restart nginx

Bước 3: Thay đổi port lắng nghe trên Apache

Chúng ta cũng cần phải thay đổi port lắng nghe trên Apache 127.0.0.2:80.

Debian/Ubuntu

Trên Debian và Ubuntu, chỉnh sửa file /etc/apache2/ports.conf.

sudo nano /etc/apache2/ports.conf

Tìm 2 dòng

Listen 80
Listen 443

Thay đổi thành

Listen 127.0.0.2:80
Listen 127.0.0.2:443

Lưu cấu hình và tắt mở file, vào thư mục /etc/apache2/sites-enabled/ để chỉnh sửa virtual host.

Thay đổi

<VirtualHost *:80>

Thành

<VirtualHost 127.0.0.2:80>

Nếu bạn có sử dụng SSL thì cũng cần file thay đổi cấu hình virtual host cho SSL.

<VirtualHost *:443>

Thành

<VirtualHost 127.0.0.2:443>

Khởi động lại Apache

sudo systemctl restart apache2

Trên CentOS

Trên CentOS, chỉnh sửa file /etc/httpd/conf/httpd.conf.

sudo nano /etc/httpd/conf/httpd.conf

Tìm

Listen 80

Thay đổi thành

Listen 127.0.0.2:80

Lưu lại và tắt mở file, sau đó vào thử mục/etc/httpd/conf.d/, để chỉnh sửa virtual host.

<VirtualHost *:80>

Thay đổi thành

<VirtualHost 127.0.0.2:80>

Nếu bạn có sử dụng SSL virtual host thì cũng cần thay đổi cấu hình virtual host cho SSL

<VirtualHost *:443>

Thay đổi thành

<VirtualHost 127.0.0.2:443>

Trong file cấu hình /etc/httpd/conf.d/ssl.conf 

Listen 443 https

Thay đổi thành

Listen 127.0.0.2:443 https

Lưu lại và đóng mở file, khởi động lại Apache để thay đổi cấu hình.

sudo systemctl restart httpd

Bước 4: Cấu hình HAProxy

Cài đặt và cấu hình HAProxy.

Trên Debian/Ubuntu

sudo apt install haproxy

Trên CentOS

sudo dnf install haproxy

Chỉnh sửa file cấu hình HAProxy.

sudo nano /etc/haproxy/haproxy.cfg

Thêm đoạn cấu hình bên dưới vào cuối file, đoạn cấu hình này sẽ cho HAProxy lắng nghe ở port 80 trên địa chỉ IP Public và chuyển hướng kết nối ở port 80 thành port 443. Thay đổi IP 12.34.56.78 thành địa chỉ IP phù hợp với IP trên máy chủ của bạn.

frontend http
    bind 12.34.56.78:80
    mode http
    redirect scheme https code 301

Chúng ta cũng cần thêm đoạn cấu hình HTTPS ở front end.

frontend https
    bind 12.34.56.78:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

Sau đó chúng ta định nghĩa Nginx và Apache back end và tham số  check nói cho HAProxy thực hiện health checks với back end bằng cách gửi TCP packet.

backend nginx
    mode tcp
    option ssl-hello-chk
    server nginx 127.0.0.1:443 check

backend apache
    mode tcp
    option ssl-hello-chk
    server apache 127.0.0.2:443 check

Bạn cũng có thể định nghĩa thêm default back end với tham số bên dưới.

default_backend nginx

Chúng ta sẽ sử dụng SNI header trong kết nôi HTTPS để chuyển hướng chính xác tới back end. Ví dụ, nếu Nginx phục vụ cho domain1.com và Apache phục vụ cho domain2.com thì chúng ta thêm 2 dòng cấu hình sau:

use_backend nginx if { req_ssl_sni -i domain1.com }
use_backend apache if { req_ssl_sni -i domain2.com }

Chú ý 2 tham số  default_backenduse_backend được định nghĩa ở trên.

Trong cấu hình ở trên chúng ta sử SNI (Server Name Indication) trong TLS để phân loại HTTPS tracffic khác nhau.

  • Khi kết nối đến domain1.com, mà domain1.com ở trong TLS Client Hello, HAProxy sẽ chuyển hướng kết nối đếnnginx backend.
  • Khi kết nối đến domain2.com, mà domain2.com ở trong TLS Client Hello, HAProxy sẽ chuyển hướng kết nôi đến  apache backend.

Nếu bạn không chỉ ra domain name trong TLS Client Hello, HAproxy sẽ sử dụng default back end nginx như đã khai báo ở trên.

Lưu lại và đóng file cấu hình, khởi động lại HAProxy.

sudo systemctl restart haproxy

Bây giờ Apache, Nginx và HAProxy chạy trên cùng máy chủ.

Làm sao để chuyển hướng địa chỉ IP của Client tới Back end

Mặt đinh, Apache và Nginx chỉ có thể thấy địa chỉ IP của HAProxy. 

By default, Apache and Nginx can only see HAProxy’s IP address. Để lấy địa chỉ IP thực của của client, thêm tham số send-proxy-v2 vào đoạn cấu hình back end của HAProxy như bên dưới.

server nginx 127.0.0.1:443 send-proxy-v2 check

server apache 127.0.0.2:443 send-proxy-v2 check

Chúng ta cũng cần thêm một vài tham số cấu hình cho Nginx và Apache để  đảm bảo hoạt động, nếu không sẽ không chạy.

Nginx

Thêm tham số  proxy_protocol vào Nginx listen như bên dưới.

listen 127.0.0.2:443 ssl http2 proxy_protocol;

Sau đó thêm 2 tham số Nginx vào chỗ http { } trong file cấu hình /etc/nginx/nginx.conf 

set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;

Lưu lại và thoát, sau đó restart lại Nginx.

sudo systemctl reload nginx

Chú ý: Nếu website của bạn đang chạy phía sau Cloudflare CDN, bạn nên thay đôi 

Chú ý: Nếu website của bạn đang chạy phía sau Cloudflare CDN, bạn nên thay đổi real_ip_header proxy_protocol; thành real_ip_header CF-Connecting-IP; để hiển thị địa chỉ IP thực của client. Bạn cũng có thể thêm tham số real_ip_header tới những block file cấu hình riêng để override file cấu hình global trong /etc/nginx/nginx.conf 

Cuối cùng, khởi động lại HAProxy.

sudo systemctl restart haproxy

Apache

Nếu bạn sử dụng Apache trên Debian/Ubuntu, bạn cần phải enable remoteip module. (Mặt đinh, module này đã được enable trong CentOS)

sudo a2enmod remoteip

Sau đó bạn thêm 3 dòng cấu hình trong cấu hình virtual host của Apache.

RemoteIPProxyProtocol On
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1

Ví dụ như bên dưới.

<VirtualHost 127.0.0.2:443>
    ServerName tungdt.net
    RemoteIPProxyProtocol On
    RemoteIPHeader X-Forwarded-For
    RemoteIPTrustedProxy 127.0.0.1

Lưu lại và thoát, sau đó bạn cũng cần thay đổi tham số trong định dạng combined log file. 

sudo nano /etc/apache2/apache2.conf

Hoặc

sudo nano /etc/httpd/conf/httpd.conf

Tìm dòng bên dưới

LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Thay thế thành

LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Lưu lại và thoát, sau đó khởi động lại Apache

sudo systemctl restart apache2

Hoặc

sudo systemctl restart httpd

Chú ý: Tham số  RemoteIPProxyProtocol On chỉ có ở phiên bản Apache  2.4.31 và mới nhất. Để kiểm tra phiên bản Apache chạy lệnh

sudo apache2 -v

Hoặc

sudo httpd -v

Trên Ubuntu 18.04 có sẵn phiên bản Apache 2.4.29. Nếu webserver Apache của bạn không phù hợp với yêu cầu thi bạn nên bỏ tham số send-proxy-v2 trong file cấu hình back end HAProxy như đã định nghĩa. Trên CentOS 8 phiên bản Apache 2.4.37.

Cuối cùng, khởi động lại HAProxy

sudo systemctl restart haproxy

Cài đặt và cấu hình Let’s Encrypt SSL Certificate

Để cài đặt Let’s Encrypt certificate cho virtual host/server block, chỉ có thể sử dụng kiểu dns-01.Còn 2 kiểu http-01tls-alpn-01 sẽ không hoạt động.

Đẩu tiền, bạn cần phải cài đặt Certbot DNS plugin, chúng có vài DNS plugins có sẵn trong Debian và Ubuntu, bạn có thể tìm với từ khóa.

apt search python3-certbot-dns

Thông tin hiển thị kết quả tìm:

python3-certbot-dns-cloudflare/bionic,bionic 0.23.0-1 all
Cloudflare DNS plugin for Certbot

python3-certbot-dns-digitalocean/bionic,bionic 0.23.0-1 all
DigitalOcean DNS plugin for Certbot

python3-certbot-dns-dnsimple/bionic,bionic 0.23.0-1 all
DNSimple DNS plugin for Certbot

python3-certbot-dns-google/bionic,bionic 0.23.0-1 all
Google DNS plugin for Certbot

python3-certbot-dns-rfc2136/bionic,bionic 0.23.0-1 all
RFC 2136 DNS plugin for Certbot

python3-certbot-dns-route53/bionic,bionic 0.23.0-1 all
Route53 DNS plugin for Certbot

Trên CentOS 8, bạn có thể tìm Certbot DNS plugins từ EPEL repository. Chú ý bạn cần cài đặt Certbot từ repository mặc định của CentOS.

sudo dnf install certbot

Tìm DNS plugins từ EPEL repository.

sudo dnf install epel-release
dnf search certbot-dns

Thôn tin hiển thị kết quả tìm:

python3-certbot-dns-ovh.noarch : OVH DNS Authenticator plugin for Certbot
python3-certbot-dns-nsone.noarch : NS1 DNS Authenticator plugin for Certbot
python3-certbot-dns-gehirn.noarch : Gehirn Infrastructure Service DNS Authenticator plugin for Certbot
python3-certbot-dns-google.noarch : Google Cloud DNS Authenticator plugin for Certbot
python3-certbot-dns-linode.noarch : Linode DNS Authenticator plugin for Certbot
python3-certbot-dns-luadns.noarch : LuaDNS Authenticator plugin for Certbot
python3-certbot-dns-rfc2136.noarch : RFC 2136 DNS Authenticator plugin for Certbot
python3-certbot-dns-route53.noarch : Route53 DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudxns.noarch : CloudXNS DNS Authenticator plugin for Certbot
python3-certbot-dns-dnsimple.noarch : DNSimple DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudflare.noarch : Cloudflare DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudflare.noarch : Cloudflare DNS Authenticator plugin for Certbot
python3-certbot-dns-dnsmadeeasy.noarch : DNS Made Easy DNS Authenticator plugin for Certbot
python3-certbot-dns-sakuracloud.noarch : Sakura Cloud DNS Authenticator plugin for Certbot

Việc cài đặt những plugin này dựa vào dịch vụ DNS hosting mà bạn đang sử dụng. Tôi đang sử dụng Cloudflare, vì thế tôi sẽ sử dụng Cloudflare làm ví dụ. Chạy những lệnh bên dưới để cài đặt gói python3-certbot-dns-cloudflare trên Debian/Ubuntu.

sudo apt install python3-certbot-dns-cloudflare

Sau đó tạo file cấu hình cho Cloudflare.

sudo nano /etc/letsencrypt/cloudflare.ini

Chúng ta cần thêm địa chỉ email Cloudflare và API key vào file cấu hình này.

# Cloudflare API credentials used by Certbot
dns_cloudflare_email = tungdt@tungdt.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

Bạn có thể tìm API key của Cloudflare ở địa chỉ https://dash.cloudflare.com/profile. Chú ý Certbot Cloudflare plugin hiện tại không hỗ trợ API Tokens của Cloudflare, vì thế bạn phải sử dụng “Global API Key” để xác thực.

Lưu lại và thoát, API key sẽ bypasses two-factor authentication củaCloudflare, vì thế bạn chỉ nên cho phép user root đọc được file này.

sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Bây giờ bạn có thể chạy lệnh certbot.

sudo certbot --agree-tos -a dns-cloudflare -i nginx --redirect --hsts --staple-ocsp --email you@example.com -d www.your-domain.com,your-domain.com

Trong lệnh trên, chúng ta chỉ ra sử dụng dns-cloudflare như là authenticator cài đặt TLS certificate và sử dụng nginx plugin để tạo  HTTPS server block. Nếu bạn sử dụng Apache, bạn thay thế  nginx thànhapache.

Lệnh này sẽ hỏi bạn nhập đường dẫn chứa file .ini, nhập đường dẫn /etc/letsencrypt/cloudflare.ini.

certbot dns cloudflare

Sau khi SSL certificate được cài đặt vào file cấu hình webserver. Bạn cần cấu hình web server lắng nghe trên 127.0.0.1:443 hoặc 127.0.0.2:443. Ví dụ, nếu bạn sử dụng Nginx

listen 443 ssl

Thay đổi thành

listen 127.0.0.1:443 ssl http2

Và cũng thay đổi port 80 thành

listen 127.0.0.1:80;

Lưu lại và thoát, sau đó khởi động lại web server.

Một số lỗi thông dụng

Sai tên miền

Nếu bạn có nhiều Nginx virtual host trên một máy chủ và khi bạn nhập một tên miền, trình duyệt web sẽ đưa bạn đến một tên miền khác được lưu trữ trên cùng một máy chủ, có thể file cấu hình Nginx virtual host không được lưu dưới dạng đuôi mở rộng .conf, vì vậy Nginx nhận biết cấu hình virtual host cho tên miền này. Cũng có thể là bạn đã thiết lập bản ghi AAAA cho tên miền, nhưng bạn chưa định nghĩa cấu hình Nginx để phân phối tên miền trong IPv6.

Vấn đề ở trên cũng tương tự cho Apache.

Vi phạm giao thức mạng

Nếu bạn thiết lập send-proxy-v2 header trong cấu hình HAProxy, nhưng bạn không enable  proxy_protocol trong Nginx hoặc Apache, thì website của bạn sẽ hiển thị lỗi như bên dưới.

The site  has experienced a network protocol violation that cannot be repaired.

Lưu ý, sau khi thay đổi cấu hình bạn phải restart lại Nginx hoặc Apache.

Lỗi PR_CONNECT_RESET_ERROR

Nếu bạn enable proxy_protocol trong Nginx hoặc Apache, nhưng bạn không thiết lập send-proxy-v2 header trong cấu hình HAProxy, website của bạn sẽ hiển thị lỗi như bên dưới.

secure connection failed. PR_CONNECT_RESET_ERROR

Và bạn cũng tìm thấy lỗi trong log file Nginx hoặc Apache như bên dưới

broken header while reading PROXY protocol

Lưu ý, sau khi thay đổi cấu hình bạn phải restart lại Nginx hoặc Apache.

Chú ý, nếu bạn enable proxy protocol một trong các file cấu hình virtual host của Nginx hoặc Apache, thì cũng ngầm hiểu rằng nó cũng enable cho tất cả virtual host khác. Không có cách nào để disable proxy_protocol cho những virtual host riêng lẻ. Chính vì thế nếu bạn dự định chạy Apache và Nginx trên cùng máy chủ thì bạn nên enable proxy protocol trong Nginx và không enable trong Apache. Nếu một virual host không cần nhận IP thực của client thì cấu hình nó trong Apache.

Chú ý: Bạn có thể thiết lập Apache/Nginx virtual host mới trên địa chỉ loopback khác như 127.0.0.4. Bằng cách này, virtual host mới không enable proxy protocol.

Lỗi PR_END_OF_FILE_ERROR

Nếu bạn enable proxy protocol trong Apache/Nginx, nhưng bạn truy cập trực tiếp virtual host (bypass HAProxy), bạn sẽ thấy lỗi như bên dưới.

PR_END_OF_FILE_ERROR

Kết thúc

Tôi hi vọng bài viết này sẽ giúp bạn hiểu rõ cách Apache, Nginx và HAProxy chạy cùng trên một máy chủ.