Loopia Hosting

How to Set Up a Python Flask Application

16.09.2024.

A long time ago I was building PHP websites, and more recently I learned some Python and really liked Flask as a minimalist framework for small projects. Until recently, I used hosting with cPanel, where setting up a Python application is mostly automated. On Loopia hosting, this is not the case. Their knowledge base covers setting up PHP websites, WordPress, Joomla, or their SiteBuilder, but not Python. I needed several hours of experimentation to get everything working. That's why I'm writing this guide—for others and for myself in the future.

Advantages of Loopia Hosting

Hosting with cPanel is generally easier and faster, but you don’t learn much about how things work in the background. I needed to implement Google authentication using the OAuth2 protocol. For that, I required the OpenSSL library, which on a cPanel system is tied to the cPanel version and cannot be upgraded. The available library version was very old, and my application couldn’t run. Loopia provides a more recent version, which allows setting up my application. The only supported mode is via a CGI server, meaning the application runs only for the duration of a request. If you need something to run periodically without a request, Loopia provides Cron. It doesn’t support standard Cron syntax, but you can schedule tasks to run at fixed intervals.

Setting Up a Python Application

Here I’ll describe what worked logically for me. Prerequisites:

  1. Familiarize yourself with the options Loopia provides, as I won’t go into too much detail.
  2. Set up an SSH account.
  3. Set up an FTP account.
  4. After purchasing a domain or pointing it to the "ns1.loopia.se" and "ns2.loopia.se" servers, propagation may take up to 48 hours, so be patient before proceeding.
  5. Open a terminal and connect to your account via SSH. You’ll be in your $HOME folder, where you need to create a folder for your application. My domain is "ujagaga.in.rs", so I created:
    mkdir ujagaga.in.rs
    cd ujagaga.in.rs
    
  6. To get a minimal Python Flask "Hello World" application, you can use my automatic setup script https://github.com/ujagaga/cgi_falsk_deploy:
    curl https://raw.githubusercontent.com/ujagaga/cgi_flask_deploy/refs/heads/main/cgi_flask_deploy.sh -o cgi_flask_deploy.sh
    chmod +x cgi_flask_deploy.sh
    ./cgi_flask_deploy.sh
    

    Note: the application runs using the cgi-bin/cgi_serve.py script, so Flask's "url_for" will reflect that in URL generation. The easiest fix is to use strings, e.g., instead of:
    url_for('home')
    
    use "/".
    If you want to explore the process in detail, read on.
  7. mkdir public_html
    cd public_html
    mkdir cgi-bin
    
    Apache runs in the background and executes scripts placed in "cgi-bin" or its subfolders.
  8. Use FTP or SCP to upload your application files. I will assume your executable script is "index.py" and your application is in:
    ~/<my_domain.com>/public_html/cgi-bin
    You also need to make your Python script executable:
    cd ~/<my_domain.com>/public_html/cgi-bin
    chmod +x index.py
    
  9. Create a ".htaccess" file in "public_html" to give Apache extra instructions. The contents might look like:
    SetEnv HOME
    
    # Look for cgi-bin/index.py first, then index.html
    DirectoryIndex cgi-bin/index.py index.html
    
    # Allow Python scripts to execute
    Options +ExecCGI
    AddHandler cgi-script .py
    
    For testing, create an "index.py" file with:
    #!/usr/bin/env python3
    
    print ("Content-type:text/html\r\n\r\n")
    print ('')
    print ('')
    print ('Hello')
    print ('')
    print ('')
    print ('

    Hello World from Python

    ') print ('') print ('')
    You should now see "Hello World from Python" on your domain. Optionally, add a "public_html/index.html" file for maintenance messages when the main script is missing. Make sure the top of your script contains "#!/usr/bin/env python3" so the server knows how to interpret it.

Python Virtual Environment

A simple "Hello World" Python script works with built-in libraries, but a more complex application requires additional libraries that Apache cannot find unless its path is specified. Therefore, I created a virtual environment:

cd ~/<domain.in.rs>
python3 -m venv venv
source venv/bin/activate
pip install flask ...
Installing some libraries requires a Rust compiler, so it's good to install that in $HOME as well. Once the libraries are installed in the virtual environment, you need to adjust ".htaccess" to forward the path. First, get the absolute path:
echo $PWD
This gives something like "/www/webvol39/cm/ukz6s6j0jrlyx9o/<domain.in.rs>", referred to below as "venv_abs_path" for ".htaccess":
SetEnv HOME

DirectoryIndex cgi-bin/index.py index.html
Options +ExecCGI
AddHandler cgi-script .py

# Set paths to Python environment
SetEnv PATH /<venv_abs_path>/venv/bin:$PATH
SetEnv PYTHONPATH /<venv_abs_path>/venv/lib/python3.11/site-packages
Now the server should be able to locate the libraries for your Python application.

Handling URL Prefixes

My application is in a subfolder:

~/<domain.in.rs>/public_html/cgi-bin/<project_name>/index.py
To ensure Apache points to the correct file, ".htaccess" must be adjusted:
SetEnv HOME

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ cgi-bin/<project_name>/index.py/$1 [QSA,L]

DirectoryIndex cgi-bin/gate_server/index.py index.html
Options +ExecCGI
AddHandler cgi-script .py
SetEnv PATH /<venv_abs_path>/venv/bin:$PATH
SetEnv PYTHONPATH /<venv_abs_path>/venv/lib/python3.11/site-packages
The next issue is that Flask must be served through the CGI server. It’s not built-in; it’s invoked from Flask. You need to create a "cgi_serve.py" script in the same folder as "index.py":
#!/usr/bin/env python3

from wsgiref.handlers import CGIHandler
from index import application

CGIHandler().run(application)
This assumes your Flask app in "index.py" uses
application = Flask(__name__)
. Then modify ".htaccess" to point to "cgi_serve.py" instead of "index.py":
SetEnv HOME

RewriteEngine On
RewriteBase /

# Redirect external requests to remove /cgi-bin/cgi_serve.py from the visible URL
RewriteCond %{THE_REQUEST} \s/cgi-bin/cgi_serve.py/([^\s?]*) [NC]
RewriteRule ^ cgi-bin/cgi_serve.py/%1 [L,R=301]

# Internally rewrite clean URLs to the actual script
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ cgi-bin/cgi_serve.py/$1 [QSA,L]

DirectoryIndex cgi-bin/gate_server/cgi_serve.py index.html
Options +ExecCGI
AddHandler cgi-script .py
SetEnv PATH /<venv_abs_path>/venv/bin:$PATH
SetEnv PYTHONPATH /<venv_abs_path>/venv/lib/python3.11/site-packages