Beget shared hosting: how it works

This page describes a Beget shared (virtual) hosting account as its owner sees it. The account name is user, the home folder is /home/u/user, and the site of this example is example.com. How shared hosting compares with a VPS: Which hosting to choose. Tools used here: curl, SSH, Linux.

Beget: one author's experience

Andrei Olegovich, who wrote the article on choosing a hosting, has hosted his sites at the Russian provider Beget since 2011. In 2011 Beget was somewhere at the end of the second ten in the ratings of .ru hosting providers; he chose it almost by chance, on a friend's advice that from Saint Petersburg its "ping would be lower". By 2020 Beget held about 15 percent of the .ru market and was among the leaders of most ratings. In his view the reasons were:

Some people never register domains at their hosting provider, so as not to keep all eggs in one basket; he keeps several domains at the registrar REG.RU with their sites at Beget, and it works without trouble. Ordering through his partner link supports this site. The rest of this page shows how the account works from the inside, from nginx in front of Apache to Python through Passenger.

nginx in front of Apache

A request does not reach Apache directly. nginx accepts it first and decides what to do with it. The front answers as nginx-reuseport.

nginx answers one kind of request itself: a static file that exists on disk. A .jpg, .png, .webp, .gif, .svg, .css, .js, .txt, .ico, .woff2 or .pdf under the document root never reaches Apache. Everything else, PHP pages included, is passed on.

That is the first surprise after a move. A 301 written for an image or a CSS file in .htaccess does not run while the file exists: nginx answers 200 before Apache reads the file. The Beget-like image in the Docker test plan shows the same 200; that is the image, not the host. The full table of what runs where: Which rules are safe where.

There is a second side to this. A rewrite rule never runs here for a static file that exists, so a pattern written for pages that also catches images or scripts does no harm on Beget, and nobody notices it. It shows up only after a move to a host where Apache serves every file. That happened to a PATH_INFO rule after a move to DreamHost: DreamHost: static files go through .htaccess.

What that means for .htaccess

Apache 2.4 sits behind nginx with AllowOverride All, so .htaccess is read for every request that reaches it. PHP runs as an Apache module, not as a separate process. The probe from the server check shows the two values that matter:

vi ~/example.com/probe-7f3a.php

<?php echo "HTTPS=", var_export($_SERVER['HTTPS'] ?? null, true), " XFP=", var_export($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null, true), " SERVER_SOFTWARE=", $_SERVER['SERVER_SOFTWARE'] ?? '', " sapi=", PHP_SAPI, "\n";

On a Beget account the answer has sapi=apache2handler: PHP is inside Apache, so php_flag and php_value in .htaccess work. The other value is the proxy header: XFP='https' on an https request, because nginx ends TLS and tells Apache what the visitor used. Delete the probe afterwards:

rm ~/example.com/probe-7f3a.php

Because mod_php is present, a block such as <IfModule mod_php.c> runs here and is skipped on a host without it. That is the safe way to keep the settings when the same file goes to another provider: Safe rules, guards and rollback.

Where HTTPS ends

TLS ends in nginx. Apache sees a plain HTTP request and the header X-Forwarded-Proto: https. Apache's own %{HTTPS} variable stays off, so an https redirect that tests only %{HTTPS} loops here. The working rule tests the header:

RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

The same rule on a host that ends TLS in Apache, DreamHost for example, redirects every https request to itself. A rule that works on both hosts tests both conditions: Redirects behind an HTTPS proxy. The DreamHost side of the story: Apache alone, TLS in Apache.

Files, FTP and SSH

The account has FTP and SFTP for file transfer, and SSH for commands. The provider lists all three among the features of every plan (Beget virtual hosting). SSH access is switched on in the control panel. Log in with the account name and the password from the panel; the host is the site domain:

ssh user@example.com

The home folder is /home/u/user: the first letter of the login, then the login. The site of this example is ~/example.com/; for a PHP site its document root is ~/example.com/public_html/. The Python guides put the application in the site folder itself: ~/example.com/ holds .htaccess, passenger_wsgi.py, the application code (app.py here) and tmp/. If an existing public_html is there, save it elsewhere and remove it. FTP uploads get the server's default modes, and they are not always what Apache needs; check them before you blame the code: permissions and the upload.

DNS on Beget nameservers

To point a domain to a site at Beget, set these name servers at the registrar of the domain. A domain that uses Beget DNS is delegated to four servers:

ns1.beget.com ns2.beget.com ns1.beget.pro ns2.beget.pro

The provider's own domain uses the same four, as a run on 8 November 2026 showed:

dig +short NS beget.com

ns2.beget.com. ns2.beget.pro. ns1.beget.com. ns1.beget.pro.

On 2 November 2026 the two .pro servers, ns1.beget.pro and ns2.beget.pro, did not answer. Domains delegated to them failed to resolve, and public resolvers answered "No Reachable Authority", while the sites themselves stayed up.

When a domain stops resolving, ask the resolver which servers it tried and whether they answer. This is the answer of a domain on Beget DNS:

dig +short NS example.com @8.8.8.8

ns1.beget.com. ns1.beget.pro. ns2.beget.com. ns2.beget.pro.

dig +short A example.com @ns1.beget.pro

203.0.113.20

A timeout on the second command points at the name server, not at the site. The records themselves and the way a resolver walks the delegation: DNS. How to move a domain to another pair of servers: Switch the DNS.

Python on the basic plan

The basic shared plan is not limited to PHP: Python is listed among the supported languages (Beget virtual hosting), and Flask and Django applications run there through Passenger. The step-by-step guides are Configure Flask on virtual hosting and Installing Django on a virtual hosting. The provider's knowledge base: Beget's knowledge base article on Python sites. The tested Flask setup in the first guide builds Python into ~/.local and runs the application from the site folder.

Enter the Beget Docker environment over SSH; the prompt starts with (docker). Build Python 3.11 into ~/.local first if it is not there, as the Flask guide shows, then install Flask:

ssh localhost -p222

python3 -m pip install --upgrade pip

python3 -m pip install flask

which python3

It must print a path under ~/.local/bin/.

The application, app.py in ~/example.com/:

from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Hello from Flask on shared hosting\n"

The file Passenger looks for, passenger_wsgi.py next to it. It puts the folder that holds app.py on the import path and exposes the Flask object as application:

import sys sys.path.insert(0, "/home/u/user/example.com") from app import app as application

The .htaccess in the site root has exactly two lines: it switches Passenger on and names the interpreter built in ~/.local:

PassengerEnabled On PassengerPython /home/u/user/.local/bin/python3

Passenger starts the application on the first request. After a code change it keeps the old version until the restart file is touched:

touch ~/example.com/tmp/restart.txt

The local check of the same two files, with the import path pointed at the project folder and the Flask test client asking for the home page, answers:

200 Hello from Flask on shared hosting

That output comes from the same application code run in a local virtual environment; on the hosting the request goes through Passenger. A Django project uses the same idea with get_wsgi_application() in passenger_wsgi.py.

When you need a VPS

Shared hosting has a ceiling. A VPS is the right plan when you need one of these:

A Flask or Django site with normal traffic does not need any of that and runs on the basic plan. Move when the application starts a background worker or needs a system library the shared plan does not provide.

Test against a Beget-like server

The repository has a Docker image that copies the web layer of this page: nginx in front, Apache with mod_php behind it, the X-Forwarded-Proto header, and a document root owned by another user. Build and run it against your own site before you upload: The Beget-like image.

Article author: Arthur Isaev

Banner image