Roundcube on cPanel failing with "DB error: [14] unable to open database file
Back to blog

Roundcube on cPanel failing with "DB error: [14] unable to open database file

6/7/2026 · 5 min · Infrastructure

In recurring cPanel server incidents, the Roundcube webmail running on port :2096 or /webmail may suddenly fail and display a generic message:

Oops... something went wrong!
An internal error has occurred. Your request cannot be processed at this time.

Investigating the internal log files of cPanel and Roundcube, we find the following technical root cause:

DB Error: [14] unable to open database file
(SQL Query: INSERT INTO "session" ...)
in /usr/local/cpanel/base/3rdparty/roundcube/program/lib/Roundcube/rcube_db.php

The key point is clear: Roundcube initializes successfully but crashes when attempting to persist the user's session. This indicates a write permission error or corruption in the SQLite database layer, rather than an internal bug in the Webmail PHP application.


1. What error [14] indicates in the SQLite context#

In the SQLite engine, error code [14] maps directly to SQLITE_CANTOPEN (unable to open the database file). Within the cPanel/Roundcube ecosystem, when the failure occurs during write operations (such as INSERT INTO session), the causes are usually divided into:

  1. Inaccessible file or directory: Incorrect permissions preventing the Roundcube process (usually executed under the cPanel account user) from reading or writing to the database file.
  2. Inability to generate Lock/Journal files: SQLite needs permission to create temporary files (such as .db-journal, .db-wal, or .db-shm) in the same folder where the .db file resides. If the parent directory lacks write permissions, SQLite will fail even if the .db file has 777 permissions.
  3. Resource Exhaustion: Out of disk space or exhaustion of free inodes on the filesystem.
  4. Security Blockade: Incorrect file contexts in SELinux or virtualization jail restrictions (CageFS/CloudLinux).

2. Identifying active versions#

Before applying any fixes, validate the control panel and application versions for configuration path references:

# Check the installed cPanel version
cat /usr/local/cpanel/version

# Check the Roundcube version integrated into cPanel
cat /usr/local/cpanel/base/3rdparty/roundcube/program/include/rcube.php | grep -i version

3. First layer: account context and SQLite integrity#

Each cPanel email account has its own SQLite database to record contacts, interface preferences, and active sessions. The default path follows this format:

/home/USER/etc/domain.com/[email protected]

3.1 mandatory preventive backup routine#

Before making any manual database changes or deletions, perform a complete backup of the affected account's email settings:

# Create a secure timestamped backup directory
BACKUP_DIR="/root/roundcube-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"

# Recursively back up the account's etc directory
cp -r /home/USER/etc/ "$BACKUP_DIR/"

# Make a dedicated copy of the SQLite database
cp /home/USER/etc/domain.com/[email protected] "$BACKUP_DIR/"

echo "Backup successfully saved to: $BACKUP_DIR"

3.2 verify SQLite integrity#

If the database file is physically corrupted, SQLite will refuse write connections:

# Run internal database integrity check
sqlite3 /home/USER/etc/domain.com/[email protected] "PRAGMA integrity_check;"

# Expected output: "ok"
# If it returns any other value, the data file is physically corrupted.

# Check file size (0-byte files indicate truncation due to disk space issues)
ls -lh /home/USER/etc/domain.com/[email protected]

# Check for orphan lock or journal files retaining the database
ls -la /home/USER/etc/domain.com/[email protected]*

3.3 correct permissions and ownership#

The process executing the webmail must be the owner of the database file. Apply the secure permission baseline:

# Adjust owner and group of the account's etc directory
chown -R USER:mail /home/USER/etc

# Set restrictive permissions on folders
chmod 750 /home/USER/etc
chmod 750 /home/USER/etc/domain.com

# Set read/write permissions for the SQLite databases
chmod 640 /home/USER/etc/domain.com/*.db

3.4 recreate corrupted database#

If the PRAGMA check returned errors or if the database file is 0 bytes, force a rebuild by moving the old database:

# Rename the database to generate a new clean one on the next login
mv /home/USER/etc/domain.com/[email protected] \
   /home/USER/etc/domain.com/[email protected]

Note: Doing this resets visual preferences and local Roundcube contacts. Email messages themselves remain safe in the Maildir.


4. Second layer: PHP SQLite extension#

Roundcube requires SQLite extensions enabled in the PHP version used by the cPanel panel (via EasyApache).

Check if extensions are loaded:#

php -m | grep -i sqlite
# Should return:
# sqlite3
# pdo_sqlite

If not returned, edit the corresponding system php.ini file (/opt/cpanel/ea-phpXX/root/etc/php.ini) and uncomment or add the extensions:

extension=sqlite3
extension=pdo_sqlite

After making changes, restart the associated PHP-FPM service:

/scripts/restartsrv ea-php-fpm

5. Third layer: scanning affected users scope#

To diagnose whether the failure is isolated to a single user or affects the entire server, run a global scan for empty databases or incorrect permissions:

# Locate all Roundcube databases on the server
find /home -name "*.rcube.db" -type f 2>/dev/null

# Locate databases with zero size (sign of previous quota exhaustion)
find /home -name "*.rcube.db" -type f -size 0 -exec ls -la {} \;

6. Fourth layer: CloudLinux & CageFS virtualization#

If the server uses CloudLinux with CageFS, the users' filesystems are isolated in virtualized environments. A lock or issue in mount propagation prevents the user from writing to the database.

Verify if CloudLinux and CageFS are active:#

# Check operating system release
cat /etc/redhat-release

# Check CageFS version
cagefsctl --version

# Check if the affected user is jailed
cagefsctl --list-users | grep USER

Rebuild and force CageFS remount:#

If the error is caused by virtual directory issues, force an update of the virtual mounts:

# Update permissions and mounts for a specific user
cagefsctl --remount USER

# Force global update of CageFS temporary directories and paths
cagefsctl --force-update

7. Fifth layer: inodes, disk space, and quotas in /tmp and /home#

SQLite must create temporary journal files in the system /tmp directory and write permanently to /home. Lack of free inodes or disk space on these partitions will cause error [14].

Check global space and inodes:#

# Check /home and /tmp partitions
df -h /home /tmp
df -i /home /tmp

Check user inode and space quotas:#

# Check if the user has reached their cPanel quota limit
repquota -a | grep USER

# Measure the hosting directory size
du -sh /home/USER/

Validate /tmp permissions and sticky bit:#

The /tmp directory must have 1777 permissions (Sticky Bit) so that multiple system users can read and write their temporary files without conflicts.

ls -ld /tmp
# Should return: drwxrwxrwt
# If permissions are incorrect, fix them:
chmod 1777 /tmp

8. Sixth layer: filesystem integrity#

Storage failures or disk errors can cause the kernel to remount partitions as read-only (ro) to protect data integrity, blocking all write operations.

# Check the status of active mount points
mount | grep -E ' /home | /tmp | / '

If you find the ro flag on key partitions, perform a filesystem check (fsck) after unmounting them safely.


9. Seventh layer: SELinux and security contexts#

In RHEL-based distributions (AlmaLinux, Rocky Linux) running with SELinux set to Enforcing, security contexts can block Apache/cPanel from writing to the /home/USER/etc directory.

Safe SELinux troubleshooting approach:#

  1. Check current status:
    getenforce
  1. Temporarily set to permissive mode for quick diagnostics (max 5 minutes):
    setenforce 0
  1. Perform the login test in Roundcube.
  2. Re-enable enforcing mode immediately after the test:
    setenforce 1
  1. If the login succeeded only with SELinux disabled, restore the correct directory contexts:
    restorecon -Rv /home/USER/etc

10. Eighth layer: cPanel package and binary repair#

If all physical infrastructure layers are operational, resolve potential binary corruptions or integrated cPanel scripts.

# 1. cPanel command to validate and fix broken RPMs
/scripts/check_cpanel_rpms --fix

# 2. If necessary, reinstall the Roundcube RPM package
dnf reinstall cpanel-roundcube -y

11. Post-fix verification#

After applying the corrections, verify that the database is functioning normally:

# 1. Attempt webmail login (port 2096)
# 2. Check if a new .db file was created under the etc directory
ls -la /home/USER/etc/domain.com/[email protected]

# 3. Audit user-specific Roundcube logs for remaining exceptions
tail -n 20 /home/USER/etc/domain.com/[email protected]/../roundcube/errors.log

# 4. Check permissions and size of the newly generated database file
stat /home/USER/etc/domain.com/[email protected]

Correlation table: symptoms vs. root causes of error [14]#

Root CauseAdditional SymptomQuick DiagnosisRecommended Fix
File PermissionsDatabase belongs to rootls -la /home/USER/etc/chown -R USER:mail
Quota ExhaustedDatabase file size is 0 bytesrepquota -a or df -hIncrease user quota or clear space
Out of InodesFree GBs available, but 0 free inodesdf -iClear orphan temp/session files
CageFS LockLogin works outside webmail servicescagefsctl --list-userscagefsctl --remount USER
SELinux ContextWrite blocks found in audit.loggetenforcerestorecon -Rv /home/USER/etc
SQLite CorruptionReading database throws errorssqlite3 [file] "PRAGMA..."Move database to .corrupted

Support team triage runbook#

Follow these sequential steps when receiving Roundcube database connection tickets:


12. Automated diagnostic script (diagnose-roundcube-db14.sh)#

To streamline Level 2/3 support troubleshooting, run this diagnostic script on the server to automatically map all described layers and pinpoint potential root causes of the error:

#!/bin/bash
# diagnose-roundcube-db14.sh
# Diagnostic script for Roundcube cPanel DB Error [14].
# Must be executed as root.

set -euo pipefail

# Color configuration for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0;37m' # No Color

log_info() {
    echo -e "[${GREEN}INFO${NC}] $1"
}

log_warn() {
    echo -e "[${YELLOW}WARN${NC}] $1"
}

log_error() {
    echo -e "[${RED}ERROR${NC}] $1"
}

# 1. Verify root privileges
if [ "$EUID" -ne 0 ]; then
    log_error "This script must be executed as root."
    exit 1
fi

log_info "Starting Roundcube DB Error [14] diagnostics..."

# Arguments: cPanel user and/or optional email
CP_USER=""
EMAIL=""
if [ $# -ge 1 ]; then
    CP_USER="$1"
fi
if [ $# -ge 2 ]; then
    EMAIL="$2"
fi

# 2. System and Panel Information
log_info "--- System and Panel Versions ---"
if [ -f /usr/local/cpanel/version ]; then
    CP_VER=$(cat /usr/local/cpanel/version)
    log_info "cPanel Version: $CP_VER"
else
    log_warn "cPanel not detected or version file inaccessible."
fi

RCUBE_PATH="/usr/local/cpanel/base/3rdparty/roundcube"
if [ -d "$RCUBE_PATH" ]; then
    if [ -f "$RCUBE_PATH/program/include/rcube.php" ]; then
        RC_VER=$(grep -i "RCMAIL_VERSION" "$RCUBE_PATH/program/include/rcube.php" || grep -i "version" "$RCUBE_PATH/program/include/rcube.php" | head -n 1)
        log_info "Roundcube Version: $RC_VER"
    fi
else
    log_warn "cPanel Roundcube directory not found."
fi

# 3. Verify SQLite PHP Extensions
log_info "--- PHP SQLite Extensions ---"
if command -v php >/dev/null 2>&1; then
    PHP_OPTS=$(php -m)
    if echo "$PHP_OPTS" | grep -iq "sqlite3" && echo "$PHP_OPTS" | grep -iq "pdo_sqlite"; then
        log_info "sqlite3 and pdo_sqlite extensions are loaded in global PHP."
    else
        log_warn "sqlite3 or pdo_sqlite extensions are missing in global PHP."
    fi
else
    log_warn "php binary not found in global PATH."
fi

# 4. Check Disk Space and Inodes on Critical Partitions
log_info "--- Disk Space and Inodes ---"
check_partition() {
    local part="$1"
    if df -P "$part" >/dev/null 2>&1; then
        local space_pct=$(df -P "$part" | awk 'NR==2 {print $5}' | sed 's/%//')
        local inode_pct=$(df -iP "$part" | awk 'NR==2 {print $5}' | sed 's/%//')
        log_info "Partition $part: Space Usage: ${space_pct}%, Inode Usage: ${inode_pct}%"
        if [ "$space_pct" -gt 95 ]; then
            log_error "Critical space usage on partition $part (>95%): $space_pct%"
        fi
        if [ "$inode_pct" -gt 95 ]; then
            log_error "Critical inode usage on partition $part (>95%): $inode_pct%"
        fi
    else
        log_warn "Could not check partition for $part"
    fi
}
check_partition "/home"
check_partition "/tmp"

# Check /tmp Sticky Bit
TMP_PERM=$(stat -c "%a" /tmp 2>/dev/null || stat -c "%A" /tmp 2>/dev/null)
if [[ "$TMP_PERM" == *"1777"* || "$TMP_PERM" == *"rwxrwxrwt"* || "$TMP_PERM" == *"777"* ]]; then
    log_info "/tmp permissions: $TMP_PERM (Sticky Bit correct)"
else
    log_warn "/tmp permissions unusual: $TMP_PERM (should be 1777 / drwxrwxrwt)"
fi

# 5. Filesystem Read-Only State Check
log_info "--- Filesystem State ---"
if mount | grep -q "on /home .*\(ro\)"; then
    log_error "Partition /home is mounted as READ-ONLY (ro)!"
else
    log_info "Partition /home mounted as read-write."
fi

# 6. CloudLinux and CageFS
log_info "--- CloudLinux and CageFS ---"
if [ -f /etc/redhat-release ] && grep -qi "cloudlinux" /etc/redhat-release; then
    log_info "OS: CloudLinux detected."
    if command -v cagefsctl >/dev/null 2>&1; then
        CAGE_VER=$(cagefsctl --version 2>/dev/null || echo "Unknown")
        log_info "CageFS active: $CAGE_VER"
        if [ -n "$CP_USER" ]; then
            if cagefsctl --list-users | grep -qw "$CP_USER"; then
                log_info "User '$CP_USER' is jailed inside CageFS."
            else
                log_warn "User '$CP_USER' is NOT jailed inside CageFS."
            fi
        fi
    else
        log_warn "cagefsctl not found in system."
    fi
else
    log_info "CloudLinux not detected."
fi

# 7. SELinux Status
log_info "--- SELinux ---"
if command -v getenforce >/dev/null 2>&1; then
    SEL_STATUS=$(getenforce)
    log_info "SELinux Status: $SEL_STATUS"
    if [ "$SEL_STATUS" = "Enforcing" ]; then
        log_warn "SELinux is in Enforcing mode. Ensure proper contexts on the email etc directory."
    fi
else
    log_info "SELinux not active/installed."
fi

# 8. Map and Audit Roundcube SQLite Databases
log_info "--- Auditing Roundcube SQLite Databases ---"
find_dbs() {
    local search_path="$1"
    find "$search_path" -type f -name "*.rcube.db" 2>/dev/null
}

DB_FILES=""
if [ -n "$CP_USER" ]; then
    USER_HOME="/home/$CP_USER"
    if [ -d "$USER_HOME" ]; then
        log_info "Mapping databases for user: $CP_USER in $USER_HOME"
        if command -v quota >/dev/null 2>&1; then
            quota -vs "$CP_USER" 2>&1 | head -n 5 || true
        fi
        DB_FILES=$(find_dbs "$USER_HOME")
    else
        log_error "User home directory not found: $USER_HOME"
        exit 1
    fi
else
    log_info "No user specified. Scanning entire /home directory..."
    DB_FILES=$(find_dbs "/home")
fi

if [ -z "$DB_FILES" ]; then
    log_warn "No .rcube.db files found in search path."
else
    for db in $DB_FILES; do
        if [ -n "$EMAIL" ] && [[ "$db" != *"$EMAIL"* ]]; then
            continue
        fi

        log_info "Analyzing database: $db"
        
        local owner=$(stat -c '%U:%G' "$db")
        local perm=$(stat -c '%a' "$db")
        local size=$(stat -c '%s' "$db")
        
        log_info "  Owner/Group: $owner | Permissions: $perm | Size: $size bytes"
        
        local user_from_path=$(echo "$db" | cut -d'/' -f3)
        if [ "$owner" != "$user_from_path:mail" ] && [ "$owner" != "$user_from_path:nobody" ]; then
            log_error "  [Permission] Incorrect owner! Expected '$user_from_path:mail' or '$user_from_path:nobody', found '$owner'."
        fi
        
        if [ "$size" -eq 0 ]; then
            log_error "  [Integrity] Database file size is ZERO (0 bytes)."
        fi
        
        if command -v sqlite3 >/dev/null 2>&1; then
            local check_res=$(sqlite3 "$db" "PRAGMA integrity_check;" 2>&1 || echo "Execution Error")
            if [ "$check_res" = "ok" ]; then
                log_info "  [Integrity] SQLite PRAGMA integrity_check: OK"
            else
                log_error "  [Integrity] Physical corruption detected: $check_res"
            fi
        else
            log_warn "  sqlite3 CLI not installed. Skipping pragma integrity check."
        fi
        
        if [ -f "${db}-journal" ]; then
            log_warn "  [Locks] Active Journal temp file: ${db}-journal"
        fi
        if [ -f "${db}-wal" ]; then
            log_warn "  [Locks] Active Write-Ahead Log temp file: ${db}-wal"
        fi
    done
fi

log_info "Diagnostics completed."

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments