Roundcube no cPanel quebrando com DB error [14] unable to open database file
Voltar para blog

Roundcube no cPanel quebrando com DB error [14] unable to open database file

07/06/2026 · 6 min · E-mail

Em incidentes recorrentes em servidores cPanel, o webmail Roundcube na porta :2096 ou /webmail pode parar de responder repentinamente e exibir a mensagem genérica:

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

Investigando os logs internos do cPanel e do próprio Roundcube, a causa raiz técnica ficou evidente:

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

O ponto-chave é claro: o Roundcube é inicializado com sucesso, mas quebra no momento de persistir a sessão do usuário. Isso indica um erro de permissão de escrita ou corrupção na camada do banco de dados SQLite, e não um bug interno da aplicação PHP do Webmail.


1. O que o erro [14] significa no contexto do SQLite#

No engine do SQLite, o código de erro [14] mapeia diretamente para SQLITE_CANTOPEN (impossibilidade de abrir o arquivo de dados). No ecossistema cPanel/Roundcube, quando a falha é acionada durante operações de escrita (como INSERT INTO session), as causas geralmente estão divididas em:

  1. Diretório ou arquivo inacessível: Permissões incorretas impedindo o processo do Roundcube (geralmente executado sob o usuário da conta do cPanel) de ler ou escrever no arquivo do banco de dados.
  2. Impossibilidade de gerar Lock/Journal: O SQLite necessita de permissão para criar arquivos temporários (como .db-journal, .db-wal ou .db-shm) na mesma pasta onde o arquivo .db reside. Se a pasta pai não tiver permissão de escrita, o SQLite falhará mesmo se o arquivo .db tiver permissões 777.
  3. Agotamento de Recursos: Ausência de espaço em disco ou esgotamento de inodes livres no filesystem.
  4. Bloqueio de Segurança: Contextos incorretos de arquivos no SELinux ou restrições de enclausuramento (CageFS/CloudLinux).

2. Identificação de versões#

Antes de aplicar correções, valide as versões do painel e da aplicação para referências de caminhos de configuração:

# Verificar a versão instalada do cPanel
cat /usr/local/cpanel/version

# Verificar a versão do Roundcube integrada ao cPanel
cat /usr/local/cpanel/base/3rdparty/roundcube/program/include/rcube.php | grep -i version

3. Primeira camada: o contexto da conta e integridade do SQLite#

Cada conta de e-mail do cPanel possui um banco de dados SQLite próprio para registrar contatos, preferências da interface e sessões. O caminho padrão segue este padrão:

/home/USER/etc/domain.com/email@domain.com.rcube.db

3.1 rotina de backup preventivo obrigatório#

Antes de fazer qualquer alteração manual ou exclusão de banco de dados, execute uma cópia de segurança completa das configurações de e-mail da conta afetada:

# Criar diretório seguro de backup datado
BACKUP_DIR="/root/roundcube-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"

# Fazer backup recursivo do diretório etc da conta
cp -r /home/USER/etc/ "$BACKUP_DIR/"

# Fazer cópia dedicada do banco de dados SQLite
cp /home/USER/etc/domain.com/email@domain.com.rcube.db "$BACKUP_DIR/"

echo "Backup salvo com sucesso em: $BACKUP_DIR"

3.2 verificar a integridade do SQLite#

Se o banco de dados estiver corrompido, o SQLite recusará novas conexões de gravação:

# Executar verificação interna de integridade
sqlite3 /home/USER/etc/domain.com/email@domain.com.rcube.db "PRAGMA integrity_check;"

# Saída esperada: "ok"
# Se retornar qualquer outro valor, o arquivo de dados está fisicamente corrompido.

# Verificar o tamanho do arquivo (arquivos com 0 bytes indicam truncamento por falta de disco)
ls -lh /home/USER/etc/domain.com/email@domain.com.rcube.db

# Verificar se existem arquivos de lock ou journal órfãos retendo o banco
ls -la /home/USER/etc/domain.com/email@domain.com.rcube.db*

3.3 corrigir permissões e ownership#

O processo que executa o webmail deve ser o proprietário do arquivo. Aplique o baseline de permissões seguro:

# Ajustar dono e grupo do diretório etc da conta
chown -R USER:mail /home/USER/etc

# Definir permissões restritivas nas pastas
chmod 750 /home/USER/etc
chmod 750 /home/USER/etc/domain.com

# Definir permissões de leitura/escrita para os bancos SQLite
chmod 640 /home/USER/etc/domain.com/*.db

3.4 recriar banco corrompido#

Se o PRAGMA retornou erros ou se o arquivo possui 0 bytes, force a regeneração do arquivo de dados movendo o antigo:

# Renomear o banco para gerar um novo no próximo login
mv /home/USER/etc/domain.com/email@domain.com.rcube.db \
   /home/USER/etc/domain.com/email@domain.com.rcube.db.corrupted

Nota: Ao realizar esta ação, as preferências visuais do webmail e a lista de contatos locais do Roundcube serão resetadas. As mensagens de e-mail continuam intactas no Maildir.


4. Segunda camada: extensão PHP SQLite#

O Roundcube precisa das extensões SQLite ativas na versão do PHP utilizada pelo painel do cPanel (através do EasyApache).

Verificar se as extensões estão carregadas:#

php -m | grep -i sqlite
# Deve retornar:
# sqlite3
# pdo_sqlite

Se não retornarem, edite o arquivo php.ini do sistema (/opt/cpanel/ea-phpXX/root/etc/php.ini) correspondente e descomente ou adicione as extensões:

extension=sqlite3
extension=pdo_sqlite

Após alterar, reinicie o serviço PHP-FPM associado:

/scripts/restartsrv ea-php-fpm

5. Terceira camada: escopo global de usuários afetados#

Para diagnosticar se a falha é isolada em um usuário ou afeta todo o servidor, execute uma busca global por bancos vazios ou com permissões incorretas:

# Localizar todos os bancos do Roundcube no servidor
find /home -name "*.rcube.db" -type f 2>/dev/null

# Localizar bancos com tamanho zerado (indício de estouro de quota anterior)
find /home -name "*.rcube.db" -type f -size 0 -exec ls -la {} \;

6. Quarta camada: virtualização CloudLinux & CageFS#

Se o servidor utiliza CloudLinux com CageFS, o sistema de arquivos dos usuários é enclausurado em diretórios virtuais. Um travamento no mapeamento desses diretórios impede o usuário de gravar no banco de dados.

Verificar se o CloudLinux e o CageFS estão ativos:#

# Verificar release do sistema operacional
cat /etc/redhat-release

# Verificar a versão do CageFS
cagefsctl --version

# Listar se o usuário afetado está enclausurado
cagefsctl --list-users | grep USER

Reconstruir e forçar remount do CageFS:#

Se o erro for causado por falha no CageFS, force a atualização dos mounts virtuais:

# Atualizar as permissões e mounts de um usuário específico
cagefsctl --remount USER

# Forçar atualização global de diretórios temporários e paths do CageFS
cagefsctl --update

7. Quinta camada: inodes, disco e quotas em /tmp e /home#

O SQLite necessita criar arquivos de diário e escrita no diretório /tmp do sistema e gravar permanentemente em /home. Falta de inodes livres ou espaço nessas partições gerará o erro [14].

Verificar espaço e inodes globais:#

# Verificar partições /home e /tmp
df -h /home /tmp
df -i /home /tmp

Verificar quotas de inodes e disco por usuário:#

# Verificar se o usuário estourou sua quota do cPanel
repquota -a | grep USER

# Medir o consumo da conta
du -sh /home/USER/

Validar as permissões e o sticky bit do /tmp:#

O diretório /tmp deve possuir a permissão 1777 (Sticky Bit) para que múltiplos usuários do sistema operacional possam ler e escrever seus arquivos temporários sem interferências.

ls -ld /tmp
# Deve retornar: drwxrwxrwt
# Se a permissão estiver incorreta, corrija:
chmod 1777 /tmp

8. Sexta camada: estado do filesystem#

Quedas de storage ou falhas no disco rígido podem fazer com que o kernel remonte as partições como somente leitura (ro) para proteger a integridade dos dados, impedindo gravações.

# Verificar o estado dos mounts ativos
mount | grep -E ' /home | /tmp | / '

Caso encontre a flag ro ligada às partições principais, execute verificações de integridade (fsck) nas partições após desmontá-las.


9. Sétima camada: SELinux e contextos de segurança#

Em distribuições baseadas em RHEL (AlmaLinux, Rocky Linux) com o SELinux no modo Enforcing, políticas de segurança podem bloquear a gravação do Apache/cPanel nos diretórios /home/USER/etc.

Abordagem segura para troubleshooting de SELinux:#

  1. Validar o status atual:
    getenforce
  1. Desativar temporariamente para teste de diagnóstico rápido (limite de 5 minutos):
    setenforce 0
  1. Realizar o teste de login no Roundcube.
  2. Reativar o SELinux imediatamente após o teste:
    setenforce 1
  1. Se o login funcionou apenas com o SELinux desativado, restaure os contextos corretos da conta:
    restorecon -Rv /home/USER/etc

10. Oitava camada: reparo e reinstalação de pacotes do cPanel#

Se todas as camadas de infraestrutura física estiverem operacionais, corrija possíveis corrupções de binários ou scripts integrados do cPanel.

# 1. Comando do cPanel para validar e corrigir RPMS quebrados
/scripts/check_cpanel_rpms --fix

# 2. Se necessário, reinstale o pacote de dados do Roundcube
dnf reinstall cpanel-roundcube -y

11. Validação pós-fix#

Após aplicar as correções, ateste se o banco voltou a funcionar normalmente:

# 1. Tentar login no Webmail (porta 2096)
# 2. Verificar se o novo arquivo .db foi instanciado no diretório etc
ls -la /home/USER/etc/domain.com/email@domain.com.rcube.db

# 3. Auditar logs de erros locais do usuário em busca de exceções remanescentes
tail -n 20 /home/USER/etc/domain.com/email@domain.com/../roundcube/errors.log

# 4. Validar permissões e tamanho do banco de dados gerado
stat /home/USER/etc/domain.com/email@domain.com.rcube.db

Tabela de correlação: sintomas vs. causas do erro [14]#

Causa RaizSintoma AdicionalDiagnóstico RápidoCorreção Recomendada
Permissão de ArquivoArquivo .db pertence ao rootls -la /home/USER/etc/chown -R USER:mail
Estouro de QuotaBanco com tamanho zero (0 bytes)repquota -a ou df -hAumentar quota ou liberar espaço
Falta de InodesEspaço em disco disponível, mas sem inodesdf -iRemover arquivos temporários órfãos
Bloqueio do CageFSO erro some com login fora do webmailcagefsctl --list-userscagefsctl --remount USER
SELinux ContextBloqueios de escrita acusados no audit.loggetenforcerestorecon -Rv /home/USER/etc
Corrupção de SQLiteErros de leitura com comando sqlite3sqlite3 [arquivo] "PRAGMA..."Mover banco para .corrupted

Runbook de atendimento e triagem rápida#

Siga estes passos sequenciais ao receber chamados de erro de banco de dados do Roundcube:


12. Script de diagnóstico automatizado (diagnose-roundcube-db14.sh)#

Para agilizar o atendimento de suporte nível 2/3, utilize este script de diagnóstico no servidor para mapear automaticamente todas as camadas descritas e apontar as possíveis causas do erro:

#!/bin/bash
# diagnose-roundcube-db14.sh
# Script de diagnóstico para o erro DB Error [14] no Roundcube cPanel.
# Deve ser executado como root.

set -euo pipefail

# Configuração de Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0;37m' # Sem cor

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. Verificar privilégios de root
if [ "$EUID" -ne 0 ]; then
    log_error "Este script precisa ser executado como root."
    exit 1
fi

log_info "Iniciando diagnóstico do Roundcube DB Error [14]..."

# Argumentos: Usuário cPanel e/ou Email opcional
CP_USER=""
EMAIL=""
if [ $# -ge 1 ]; then
    CP_USER="$1"
fi
if [ $# -ge 2 ]; then
    EMAIL="$2"
fi

# 2. Informações do Painel cPanel e Roundcube
log_info "--- Versões do Sistema e Painel ---"
if [ -f /usr/local/cpanel/version ]; then
    CP_VER=$(cat /usr/local/cpanel/version)
    log_info "cPanel Versão: $CP_VER"
else
    log_warn "cPanel não detectado ou versão inacessível."
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 Versão: $RC_VER"
    fi
else
    log_warn "Diretório do Roundcube do cPanel não localizado."
fi

# 3. Verificar extensões SQLite no PHP
log_info "--- Extensões PHP SQLite ---"
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 "Extensões sqlite3 e pdo_sqlite carregadas no PHP global."
    else
        log_warn "Extensões sqlite3 ou pdo_sqlite ausentes no PHP global."
    fi
else
    log_warn "Binário php não encontrado no PATH global."
fi

# 4. Verificar espaço em disco e inodes nas partições críticas
log_info "--- Espaço em Disco e 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 "Partição $part: Uso de espaço: ${space_pct}%, Uso de inodes: ${inode_pct}%"
        if [ "$space_pct" -gt 95 ]; then
            log_error "Espaço crítico na partição $part (>95%): $space_pct%"
        fi
        if [ "$inode_pct" -gt 95 ]; then
            log_error "Uso crítico de inodes na partição $part (>95%): $inode_pct%"
        fi
    else
        log_warn "Não foi possível verificar a partição para $part"
    fi
}
check_partition "/home"
check_partition "/tmp"

# Verificar Sticky Bit de /tmp
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 permissões: $TMP_PERM (Sticky Bit correto)"
else
    log_warn "/tmp permissões incomuns: $TMP_PERM (deveria ser 1777 / drwxrwxrwt)"
fi

# 5. Estado do Filesystem (ReadOnly check)
log_info "--- Estado do Filesystem ---"
if mount | grep -q "on /home .*\(ro\)"; then
    log_error "Partição /home está montada como SOMENTE LEITURA (ro)!"
else
    log_info "Partição /home montada como leitura/escrita."
fi

# 6. CloudLinux e CageFS
log_info "--- CloudLinux e CageFS ---"
if [ -f /etc/redhat-release ] && grep -qi "cloudlinux" /etc/redhat-release; then
    log_info "SO: CloudLinux detectado."
    if command -v cagefsctl >/dev/null 2>&1; then
        CAGE_VER=$(cagefsctl --version 2>/dev/null || echo "Desconhecida")
        log_info "CageFS ativo: $CAGE_VER"
        if [ -n "$CP_USER" ]; then
            if cagefsctl --list-users | grep -qw "$CP_USER"; then
                log_info "Usuário '$CP_USER' está enjaulado no CageFS."
            else
                log_warn "Usuário '$CP_USER' NÃO está enjaulado no CageFS."
            fi
        fi
    else
        log_warn "cagefsctl não encontrado no sistema."
    fi
else
    log_info "CloudLinux não detectado."
fi

# 7. Status do SELinux
log_info "--- SELinux ---"
if command -v getenforce >/dev/null 2>&1; then
    SEL_STATUS=$(getenforce)
    log_info "Status do SELinux: $SEL_STATUS"
    if [ "$SEL_STATUS" = "Enforcing" ]; then
        log_warn "SELinux está em Enforcing. Certifique-se de que os contextos do diretório etc de e-mail estão corretos."
    fi
else
    log_info "SELinux não ativo/instalado."
fi

# 8. Mapear e auditar bancos de dados do Roundcube
log_info "--- Auditoria dos Bancos de Dados SQLite do Roundcube ---"
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 "Mapeando bancos para o usuário: $CP_USER em $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 "Diretório home do usuário não encontrado: $USER_HOME"
        exit 1
    fi
else
    log_info "Nenhum usuário especificado. Escaneando todo o diretório /home..."
    DB_FILES=$(find_dbs "/home")
fi

if [ -z "$DB_FILES" ]; then
    log_warn "Nenhum arquivo .rcube.db localizado no caminho de busca."
else
    for db in $DB_FILES; do
        if [ -n "$EMAIL" ] && [[ "$db" != *"$EMAIL"* ]]; then
            continue
        fi

        log_info "Analisando banco: $db"
        
        owner=$(stat -c '%U:%G' "$db")
        perm=$(stat -c '%a' "$db")
        size=$(stat -c '%s' "$db")
        
        log_info "  Dono/Grupo: $owner | Permissões: $perm | Tamanho: $size bytes"
        
        user_from_path=$(echo "$db" | cut -d'/' -f3)
        if [ "$owner" != "$user_from_path:mail" ] && [ "$owner" != "$user_from_path:nobody" ]; then
            log_error "  [Permissão] Proprietário incorreto! Esperado '$user_from_path:mail' ou '$user_from_path:nobody', encontrado '$owner'."
        fi
        
        if [ "$size" -eq 0 ]; then
            log_error "  [Integridade] Arquivo de banco de dados está com tamanho ZERADO (0 bytes)."
        fi
        
        if command -v sqlite3 >/dev/null 2>&1; then
            check_res=$(sqlite3 "$db" "PRAGMA integrity_check;" 2>&1 || echo "Erro de Execução")
            if [ "$check_res" = "ok" ]; then
                log_info "  [Integridade] SQLite PRAGMA integrity_check: OK"
            else
                log_error "  [Integridade] Corrupção física detectada: $check_res"
            fi
        else
            log_warn "  sqlite3 CLI não instalada. Pulando pragma integrity_check."
        fi
        
        if [ -f "${db}-journal" ]; then
            log_warn "  [Locks] Arquivo temporário de Journal ativo: ${db}-journal"
        fi
        if [ -f "${db}-wal" ]; then
            log_warn "  [Locks] Arquivo temporário Write-Ahead Log ativo: ${db}-wal"
        fi
    done
fi

log_info "Diagnóstico concluído."

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários