CloudLinux Symlink Protection: Fix 403/500 in cPanel
You create a symlink — maybe to share an uploads folder between two sites, or to point a cache directory somewhere else — and the page behind it starts throwing a 403 Forbidden or a bare 500 Internal Server Error. Nothing else on the account changed. What you've run into is CloudLinux's Symlink Race Condition Protection, a kernel-level security feature that most cPanel users never hear about until it breaks something.
Symptom: What You're Actually Seeing
- The URL behind a symlinked file or folder returns 403 or 500, but the same content opened through its real, non-symlinked path works fine.
- A PHP
include()orrequire()that points through a symlink fails withfailed to open stream: Permission denied, even thoughls -lshows normal-looking permissions. - The Apache error log has a line like
Symbolic link not allowed or link target not accessibleright around the timestamp of the failed request. - It usually starts right after you create a new symlink, restore an account from backup, or migrate a site between servers.
Cause: Why CloudLinux Blocks the Symlink
On CloudLinux-based cPanel servers — which is most shared, reseller, and a fair number of managed VPS setups — the kernel itself enforces symlink ownership checks, independent of whatever Options line you have in .htaccess. This exists to stop a known class of attack: on a shared server, if account A could create a symlink pointing into account B's files and trick a process into following it, A could read or overwrite B's data. CloudLinux closes that gap by requiring, for every symlink Apache or PHP follows:
- The symlink and its target must be owned by the exact same user (same UID).
- None of the directories in the path to the target can be group- or world-writable, unless the sticky bit is set on them.
- The target file or directory itself must not be world-writable.
Fail any one of those and the kernel refuses to resolve the link for the web server or PHP process — even though root, logged in directly, can follow it without any trouble at all.
| What you see | Usual cause |
|---|---|
| 403 Forbidden on the symlinked URL | Symlink target is owned by a different cPanel user than the symlink itself |
| 500 Internal Server Error | Target lives inside a directory that's group- or world-writable (often left at 777 from an old fix) |
open_basedir restriction in effect in PHP logs | Symlink ownership passed, but the resolved real path falls outside PHP's open_basedir allow-list |
The Fix
1. Confirm it really is symlink protection
Check the Apache error log for the account (WHM > Server Configuration > Terminal, or via SSH):
tail -f /usr/local/apache/logs/error_log | grep -i symb
A hit on "Symbolic link not allowed" confirms it — you're not chasing a permissions typo or a caching issue.
2. Check whether ownership actually matches
stat -c '%U %n' /home/user1/public_html/shared
stat -c '%U %n' /home/user2/public_html/uploads
If the two usernames differ, that's the whole problem.
3. Same-account symlink: fix ownership and permissions
chown -h user1:user1 /home/user1/public_html/shared
find /home/user1/public_html/uploads -type d -exec chmod 755 {} \;
find /home/user1/public_html/uploads -type f -exec chmod 644 {} \;
The -h flag on chown matters — without it you'll change the ownership of whatever the link points to instead of the link itself.
4. Cross-account symlink: don't fight the protection
If the target genuinely belongs to a different cPanel user, CloudLinux will not let this through, by design, and root loosening it defeats the entire point of the feature on a shared server. Instead:
- Sync the files with a scheduled
rsyncor cron job instead of linking to them live. - Put both sites under the same cPanel account as addon domains, so they share one owner and one home directory.
- Serve the shared assets from a subdomain under the account that already owns them, and reference it by URL rather than by filesystem path.
5. Rule out open_basedir separately
Even after ownership checks pass, PHP can still block the resolved path. In MultiPHP INI Editor, add the real target directory (not just the symlink path) to open_basedir for that domain, then retest.
6. Retest immediately
curl -I https://example.com/shared/file.jpg
Don't rely on a browser reload — a cached 403 can look identical to a fresh one. curl -I gives you the real status without any caching in the way.
If You Have Root or WHM Access
CloudLinux does let root loosen symlink owner-match checks server-wide or per LVE, but treat that as a last resort reserved for genuinely single-tenant boxes — a VPS running one cPanel account, where there's no second user to protect against. On any shared or reseller server, leave it on. It costs you nothing when everything is one owner, and it's the exact control that stops one hacked account from reaching into another.
Prevention
- Run
chown -hon a symlink the moment you create it — don't leave it to inherit whatever the parent directory had. - Never symlink across two different cPanel accounts; sync or restructure instead.
- Keep directories at 755 and files at 644. Resist the urge to
chmod -R 777a folder to "just make it work" — that's usually what breaks symlink protection a week later. - After any account restore, migration, or WHM transfer, re-check ownership on every symlink in the account —
pkgacct/restorepkgcan shift UIDs even when paths stay identical. - Document any symlinks you rely on. They're invisible in a normal file listing at a glance and easy for the next person (or future you) to break without realizing it.
Frequently asked questions
Why did this symlink work yesterday and break today?
Nothing about the link itself needs to change for this to happen. A backup restore, a WHM account transfer, or a CloudLinux update can shift ownership or tighten enforcement without touching the symlink's target path at all. Re-check ownership with `stat` right after any migration or restore.
Can I just ask my host to disable Symlink Protection?
On shared or reseller hosting, no reputable host will do this account-wide since it reopens a known cross-account attack path. It's only realistic to disable on a single-tenant VPS where there's no second account to protect against.
Does it matter whether I created the symlink via SSH or cPanel File Manager?
No. The check happens at the kernel level for any process reading through the link, so Apache, PHP-FPM, and an SSH shell are all subject to the same ownership rules regardless of how the link was created.
I fixed the ownership but PHP still throws a permission error — why?
That's usually open_basedir, a separate PHP-level restriction. Once the OS-level symlink check passes, PHP still checks the resolved real path against its own allow-list. Add the target directory to open_basedir in MultiPHP INI Editor.
I'm on a Getwebup VPS with root access — should I turn this off?
If it's a single cPanel account on that VPS, the same-owner rule is close to moot since everything already shares one user. It still costs nothing to leave it enabled, so there's rarely a reason to switch it off unless you fully understand what you're trading away.