Why a default Moodle install is exposed
Moodle stores three things that attackers care about: the application code, a MySQL/MariaDB database, and a data directory called moodledata that holds session files, uploaded assignments, cached content, and temporary backups. On a properly designed server the moodledata directory sits above the web root so it can never be requested over HTTP. On shared hosting the installer frequently drops it inside public_html because that was the only writable path the operator picked during setup. When that happens, files such as student submissions and session tokens become reachable through a browser if someone guesses or scrapes the path.
The second common weakness is permissions. Moodle's config.php contains the database username, password, and the $CFG->dataroot location. If that file is left at 0644 or, worse, 0666 after an FTP upload, it is readable in ways it should not be, and any writable-by-group setting invites tampering. The third weakness is the login and administration surface: /login/index.php and /admin/ accept unlimited requests by default, which makes credential stuffing and automated account probing cheap for attackers.
None of these problems require root to fix. Every remedy below runs entirely inside your hosting account using cPanel Jupiter or DirectAdmin Evolution File Manager, phpMyAdmin, the .htaccess and .user.ini mechanisms LiteSpeed honors, and the Moodle Site Administration dashboard. Before changing anything, take a full backup: in cPanel use Files > Backup > Download a Full Account Backup, or in DirectAdmin use System Backup / Create Backup, so you can roll back if a rule breaks the site.
Relocate moodledata and correct file permissions
First confirm where your data directory lives. Open public_html/config.php in File Manager (right-click > Edit) and read the $CFG->dataroot line. If it points somewhere inside public_html, that is the primary issue to solve.
The cleanest fix is to move the directory out of the web root entirely. Using File Manager, create a new folder at the account root, for example /home/youruser/moodledata, then move the existing files into it (cPanel File Manager supports drag-and-drop or the Move button; make sure hidden files are shown via Settings > Show Hidden Files). After the move, edit config.php so the path matches:
$CFG->dataroot = '/home/youruser/moodledata';Replace youruser with your actual account name, which you can confirm in the top corner of cPanel or on the DirectAdmin dashboard. Load the site and a course page afterward to confirm sessions and file serving still work.
If your hosting layout does not allow a directory above the web root to be used by PHP, keep moodledata where it is but block all HTTP access with a rule placed in a .htaccess file inside the moodledata folder itself:
# moodledata/.htaccess
Require all denied
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>LiteSpeed reads Apache-style .htaccess directives, so this denies every direct request while PHP continues to read the files through the filesystem. Test it by trying to open https://yourdomain.com/moodledata/sessions/ in a browser; you should receive a 403.
Now fix permissions. Moodle's guidance is that config.php should not be writable by the web process once installation is finished, and that directories use 0755 with files at 0644. In File Manager, right-click config.php, choose Change Permissions, and set it to 0644 (owner read/write, group and world read only). Set the moodledata directory to 0755 and never leave any Moodle directory at 0777. If a plugin install once required a writable directory and you loosened it, tighten it back afterward. Under CloudLinux the account already runs as your own user, so 0644 and 0755 give the PHP worker everything it needs without exposing files to other tenants.
Protect admin paths and slow down brute force
Moodle has built-in defenses you should switch on before touching web-server rules. In the dashboard go to Site administration > Plugins > Authentication > Manage authentication and, in the common settings, review the account lockout options. Then open Site administration > Security > Site security settings and enable Account lockout threshold, set a lockout window, and turn on Log in via email restrictions if appropriate. On the same screen, enable Force users to log in and set Maximum time to edit posts conservatively. Under Site administration > Security > HTTP security, tick Use HTTPS for logins equivalents and set the cookie flags described in the next section.
To reduce raw request volume against the login page, add IP-based protection in the root .htaccess. If your administrators connect from a known office or VPN address, restrict the admin directory to those addresses:
# public_html/.htaccess
<IfModule mod_authz_core.c>
<Files "admin">
Require ip 203.0.113.24
</Files>
</IfModule>Because /admin/ is a directory rather than a single file, a more reliable approach on LiteSpeed is a directory-scoped .htaccess placed at public_html/admin/.htaccess:
# public_html/admin/.htaccess
Require ip 203.0.113.24
Require ip 198.51.100.0/24Swap in your real static IPs. If your team uses dynamic IPs this will lock you out, so only apply it where addresses are stable. For the login form itself, add HTTP Basic authentication as a second gate. Use cPanel's Directory Privacy tool (or DirectAdmin's Password Protected Directories) to protect a folder, which generates an .htpasswd file for you and writes the matching AuthType directives. Point it at a small wrapper directory or apply it selectively, since protecting the whole site would block students. A pragmatic middle ground is limiting request methods and blocking known abusive user agents in the root .htaccess:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) [NC]
RewriteRule .* - [F]
</IfModule>Also review Site administration > Server > Support contact and the Registration settings under Plugins > Authentication. Disable open self-registration unless you genuinely need it; if you keep it, enable email confirmation and add reCAPTCHA under Site administration > Plugins > Authentication > Manage authentication to stop spam account creation.
Add HTTP security headers and harden config.php
Moodle serves plenty of user-generated content, so browser-side protections matter. Add a security header block to the root public_html/.htaccess. LiteSpeed applies these on every response:
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>Only send Strict-Transport-Security once you have a valid SSL certificate installed and the site loads cleanly over HTTPS; issue the certificate through cPanel SSL/TLS Status > Run AutoSSL or DirectAdmin SSL Certificates > Let's Encrypt first. Avoid a strict Content-Security-Policy header at the web-server level for Moodle, because the platform loads many inline scripts and plugin assets; a mismatched policy will silently break the editor and grading interfaces. Manage CSP inside Moodle instead, under Site administration > Security > HTTP security, where the platform generates rules aware of its own assets.
Reinforce cookie and proxy settings directly in config.php just below the existing definitions:
$CFG->cookiesecure = true;
$CFG->cookiehttponly = true;
$CFG->wwwroot = 'https://yourdomain.com';Setting wwwroot to the exact HTTPS address prevents Moodle from generating mixed-content links, and the cookie flags stop session identifiers from traveling over plain HTTP or being read by JavaScript. Confirm $CFG->wwwroot has no trailing slash and matches the certificate exactly, including www or its absence.
Finally, block direct access to backup and sensitive files that occasionally end up in the web root. Add to public_html/.htaccess:
<FilesMatch "\.(sql|bak|old|log|ini)$">
Require all denied
</FilesMatch>Note that this also denies .user.ini; that file is read by PHP from disk, not over HTTP, so blocking web requests to it is exactly what you want. If you use .user.ini to raise limits for large course backups, place directives such as upload_max_filesize and post_max_size there through the PHP Selector's environment editor rather than editing any server-wide file. After every change, watch the account-level error_log that appears in the affected directory through File Manager; a sudden 500 error after adding a Header or Require line usually means the relevant module name differs on your server, and removing that single block restores service while you adjust the syntax.