Another Nginx SNI Example with Ghost & BeamCafe & Pinry Deployment

1. Intro
Ghost is a free and open source blogging platform written in JavaScript and distributed under the MIT License, designed to simplify the process of online publishing for individual bloggers as well as online publications.
BeamCafe knows very little about you - the names of your files, your IP and well, that's it. If you send over a file it won't get saved somewhere on the server but instead will be streamed from your local machine over the server of beam.cafe directly to your peer. You can always check who's downloading or streaming your files and, in case a link fell into the wrong hands, invalidate a file.
Pinry, a tiling image board system for people who want to save, tag, and share images, videos and webpages in an easy to skim through format.
2. Background
This is an example of the use of Nginx SNI to deploy three applications on the same port 443.
Environment: Ubuntu 20.04 LTS
Ghost & Pinry deployment via Docker
Beam.Cafe deployment via bare VPS setup
3. Dependencies
Install the required packages
apt -y update
apt -y install build-essential nginx python3-certbot-nginx git curl
curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash -
apt -y install nodejs
npm install -g pm2
Install Docker
curl -sSL https://get.docker.com/ | sh
systemctl enable --now nginx docker
Install docker-compose (check latest version yourself)
curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
4. Ghost Deployment
Create directory and edit docker-compose file
mkdir -p /opt/ghost && cd /opt/ghost && nano docker-compose.yml
Fill in the followings:
version: '3.7'
services:
ghost:
image: ghost:3-alpine
restart: always
ports:
- 127.0.0.1:3000:2368
environment:
database__client: mysql
database__connection__host: db
database__connection__user: root
database__connection__password: password #edit
database__connection__database: ghost
url: https://ghost.example.com #your domain
mail__transport: SMTP
mail__from: user <[email protected]> # optional, your email of own domain
mail__options__service: SMTP
mail__options__host: smtp.youremailserver.com # optional, eg. gmail
mail__options__port: 587
mail__options__auth__user: [email protected] # optional, eg. gmail
mail__options__auth__pass: your_auth_pass # optional, eg. gmail
volumes:
- /opt/ghost/content:/var/lib/ghost/content
db:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: password # edit
volumes:
- /opt/ghost/mysql:/var/lib/mysql
Build up
docker-compose up -d
5. BeamCafe Deployment
Pull front-end and back-end project files
cd /opt
git clone https://github.com/dot-cafe/beam.cafe.git
git clone https://github.com/dot-cafe/beam.cafe.backend.git
To build the front end, first enter the project directory
cd /opt/beam.cafe
Then edit this file
nano webpack.config.prod.js
Replace the domain name of WS_ENDPOINT and HTTPS_ENDPOINT with your own:
plugins: [
new webpack.DefinePlugin({
'env': {
'NODE_ENV': JSON.stringify('production'),
'VERSION': JSON.stringify(resolveAppVersion()),
'BUILD_DATE': JSON.stringify(Date.now()),
'WS_ENDPOINT': JSON.stringify('wss://beamcafe.example.com/ws'),
'HTTPS_ENDPOINT': JSON.stringify('https://beamcafe.example.com')
}
}),
Then install the dependencies and you can build
npm install
npm run build
Move the built front-end resource files to the Nginx site directory
mv dist/ /var/www/beam
Next, build the backend
cd /opt/beam.cafe.backend
npm install
npm run build
After the backend is built, use pm2 to start the backend and set it to start automatically
pm2 start dist/src/app.js --name beam.cafe.backend
pm2 startup
pm2 save
6. Pinry Deployment
Create a new directory and docker-compose file
mkdir -p /opt/pinry && cd /opt/pinry && nano docker-compose.yml
Fill in the followings:
version: "3.7"
services:
pinry:
image: getpinry/pinry
container_name: pinry
restart: always
ports:
- 9090:80
volumes:
- /opt/pinry/data:/data
Build up
docker-compose up -d
Next, use the following command to create an administrator account
docker-compose exec pinry python manage.py createsuperuser --settings=pinry.settings.docker
If necessary, you should now edit the Pinry configuration file
nano /opt/pinry/data/local_settings.py
Some functions of Pinry can be set here:
ALLOW_NEW_REGISTRATIONS = False # close registration
PUBLIC = False # show content only after login
After modifying the Pinry configuration, to make it effective, the Pinry container must be restarted
docker-compose restart pinry
7. Nginx Configuration
7.1 Nginx main config file
nano /etc/nginx/nginx.conf
Add the following configuration outside the “http block”
stream {
map $ssl_preread_server_name $example_multi {
ghost.example.com ghost;
beamcafe.example.com beamcafe;
pinry.example.com pinry;
}
upstream ghost {
server 127.0.0.1:20001;
}
upstream beamcafe {
server 127.0.0.1:20002;
}
upstream pinry {
server 127.0.0.1:20003;
}
server {
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $example_multi;
ssl_preread on;
}
}
7.2 HTTP Redirect
nano /etc/nginx/conf.d/httpredirect.conf
#HTTP Redirect
server {
listen 80;
server_name ghost.example.com;
if ($host = ghost.example.com) {
return 301 https://$host$request_uri;
}
server_name beamcafe.example.com;
if ($host = beamcafe.example.com) {
return 301 https://$host$request_uri;
}
server_name pinry.example.com;
if ($host = pinry.example.com) {
return 301 https://$host$request_uri;
}
return 404;
}
7.3 Ghost config file
nano /etc/nginx/sites-available/ghost.example.com
server {
listen 127.0.0.1:20001 ssl; # match port in nginx.conf
ssl_certificate /your/path/to/cert/example_cert.pem;
ssl_certificate_key /your/path/to/cert/example_key.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
server_name ghost.example.com; # your domain
location / {
proxy_redirect off;
proxy_pass http://127.0.0.1:3000; # match port set in your Ghost docker-compose
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
7.4 BeamCafe config file
nano /etc/nginx/sites-available/beamcafe.example.com
server {
listen 127.0.0.1:20002 ssl; # match port in nginx.conf
server_name beamcafe.example.com; # your domain
client_max_body_size 0;
root /var/www/beam;
index index.html;
ssl_certificate /your/path/to/cert/example_cert.pem;
ssl_certificate_key /your/path/to/cert/example_key.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
# client_max_body_size 0;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
location ~ ^/(d|b) {
proxy_pass http://127.0.0.1:8080$request_uri;
proxy_buffering off;
proxy_request_buffering off;
}
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
7.5 Pinry config file
nano /etc/nginx/sites-available/pinry.example.com
server {
listen 127.0.0.1:20003 ssl;
ssl_certificate /your/path/to/cert/example_cert.pem;
ssl_certificate_key /your/path/to/cert/example_key.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
server_name pinry.example.com;
add_header Content-Security-Policy upgrade-insecure-requests;
location / {
proxy_redirect off;
proxy_pass http://127.0.0.1:9090;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
7.6 Create soft links
ln -s /etc/nginx/sites-available/ghost.example.com /etc/nginx/sites-enabled/ghost.example.com
ln -s /etc/nginx/sites-available/beamcafe.example.com /etc/nginx/sites-enabled/ghost.example.com
ln -s /etc/nginx/sites-available/pinry.example.com /etc/nginx/sites-enabled/ghost.example.com
7.7 Get SSL
You can get SSL cert via CertBot
certbot --nginx
Or, set path directly if you already have the wildcard certificates.
Use CloudFlare Certificate with Nginx + V2RAY + WebSocket +TLS + CDN
7.8 Ready to start
First, make sure the ports 3000(Ghost), 8080(BeamCafe), 9090(Pinry) are all being listened.
netstat -nltp
Check Nginx
nginx -t
Restart Nginx
systemctl restart nginx
8. All set
Do not forget to set DNS record of your subdomains.
If you follow the steps above, now you can visit
- Ghost: https://ghost.example.com
- BeamCafe: https://beamcafe.example.com
- Pinry: https://pinry.example.com
All three sites are with SSL and via port 443.
Enjoy.
Copyright statement: Unless otherwise stated, all articles on this blog adopt the CC BY-NC-SA 4.0 license agreement. For non-commercial reprints and citations, please indicate the author: Henry, and original article URL. For commercial reprints, please contact the author for authorization.