Ready-made solutions for Hestia CP were not found. Here are some helpful thoughts in these articles: https://github.com/realjumy/hestiacp-python-templates https://help.clouding.io/hc/es/articles/4408217149458-C%C3%B3mo-instalar-Django-y-Gunicorn-en-HestiaCP
Therefore, I did it simply in 2 stages:
In our case, Apache is not used, and Hestia CP panel via interface only offers templates in the php-fpm folder. This means we cannot use sh scripts. There are suitable no-php.* templates.
Let's make copies:
cd /usr/local/hestia/data/templates/web/nginx/php-fpm
cp no-php.tpl proxy_gunicorn.tpl
cp no-php.stpl proxy_gunicorn.stpl
Edit the templates:
nano proxy_gunicorn.tpl
nano proxy_gunicorn.stpl
To proxy Gunicorn through Nginx, we only need to take a couple of config snippets from the official documentation and create templates based on them:
location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
I changed proxy_pass from the documentation to "http://127.0.0.1:8000" and also commented out some lines in the original templates (in particular, the line with the index parameter and the fragment describing location ~* ^.+.(jpeg|jpg|png|webp|gif|bmp|ico|svg|css|js) ... ). No other changes were necessary.
In the Hestia CP panel settings, simply select our template, and for php-fpm - "no-php".
The result is the following configuration for Hestia CP:
proxy_gunicorn.tpl
#=========================================================================#
# Default Web Domain Template #
# DO NOT MODIFY THIS FILE! CHANGES WILL BE LOST WHEN REBUILDING DOMAINS #
# https://hestiacp.com/docs/server-administration/web-templates.html #
#=========================================================================#
server {
listen %ip%:%web_port%;
server_name %domain_idn% %alias_idn%;
root %docroot%;
# index index.php index.html index.htm;
access_log /var/log/nginx/domains/%domain%.log combined;
access_log /var/log/nginx/domains/%domain%.bytes bytes;
error_log /var/log/nginx/domains/%domain%.error.log error;
include %home%/%user%/conf/web/%domain%/nginx.forcessl.conf*;
location ~ /\.(?!well-known\/) {
deny all;
return 404;
}
location / {
# location ~* ^.+\.(jpeg|jpg|png|webp|gif|bmp|ico|svg|css|js)$ {
# expires max;
# fastcgi_hide_header "Set-Cookie";
# }
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
location ~ [^/]\.php(/|$) {
types { } default_type "text/html";
}
location /error/ {
alias %home%/%user%/web/%domain%/document_errors/;
}
location /vstats/ {
alias %home%/%user%/web/%domain%/stats/;
include %home%/%user%/web/%domain%/stats/auth.conf*;
}
include /etc/nginx/conf.d/phpmyadmin.inc*;
include /etc/nginx/conf.d/phppgadmin.inc*;
include %home%/%user%/conf/web/%domain%/nginx.conf_*;
}
proxy_gunicorn.stpl
#=========================================================================#
# Default Web Domain Template #
# DO NOT MODIFY THIS FILE! CHANGES WILL BE LOST WHEN REBUILDING DOMAINS #
# https://hestiacp.com/docs/server-administration/web-templates.html #
#=========================================================================#
server {
listen %ip%:%web_ssl_port% ssl;
server_name %domain_idn% %alias_idn%;
root %sdocroot%;
# index index.php index.html index.htm;
access_log /var/log/nginx/domains/%domain%.log combined;
access_log /var/log/nginx/domains/%domain%.bytes bytes;
error_log /var/log/nginx/domains/%domain%.error.log error;
ssl_certificate %ssl_pem%;
ssl_certificate_key %ssl_key%;
ssl_stapling on;
ssl_stapling_verify on;
# TLS 1.3 0-RTT anti-replay
if ($anti_replay = 307) { return 307 https://$host$request_uri; }
if ($anti_replay = 425) { return 425; }
include %home%/%user%/conf/web/%domain%/nginx.hsts.conf*;
location ~ /\.(?!well-known\/) {
deny all;
return 404;
}
location / {
# location ~* ^.+\.(jpeg|jpg|png|webp|gif|bmp|ico|svg|css|js)$ {
# expires max;
# fastcgi_hide_header "Set-Cookie";
# }
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
location ~ [^/]\.php(/|$) {
types { } default_type "text/html";
}
location /error/ {
alias %home%/%user%/web/%domain%/document_errors/;
}
location /vstats/ {
alias %home%/%user%/web/%domain%/stats/;
include %home%/%user%/web/%domain%/stats/auth.conf*;
}
proxy_hide_header Upgrade;
include /etc/nginx/conf.d/phpmyadmin.inc*;
include /etc/nginx/conf.d/phppgadmin.inc*;
include %home%/%user%/conf/web/%domain%/nginx.ssl.conf_*;
}
As root, first install the package to set up venv:
apt install python3.12-venv
Then in the site folder created via Hestia CP, set up a Python venv and test it in the console:
python3 -m venv /home/user/web/example.com/public_html
cd /home/user/web/example.com/public_html/
source bin/activate
pip install dash
pip install gunicorn
pip install python-dateutil
pip install pandas
pip install dash-bootstrap-components
pip install gevent
bin/gunicorn -b 127.0.0.1:8000 app:server
Install the package:
apt install supervisor
Configure it:
cd /etc/supervisor/conf.d
echo "[program:example_com]
command=source /home/user/web/example.com/public_html/bin/activate && bin/gunicorn -b 127.0.0.1:8000 app:server
directory=/home/user/web/example.com/public_html/
user=user
autostart=true
autorestart=true
redirect_stderr=true" > example_com.conf
The service is managed by the following commands:
supervisorctl status example_com
supervisorctl start example_com
supervisorctl restart example_com
supervisorctl stop example_com
You can also completely restart the supervisor service:
systemctl restart supervisor