Tag: apache

  • Implantação do Hestia: um painel de controle de hospedagem de sites

    Hestia Control Panel é uma ferramenta de administração de hospedagem de código aberto para Linux. Ele abrange diferentes serviços e configurações relacionadas a hospedagem de sites, incluindo usuários, zonas de DNS, bancos de dados, e-mail e versões de PHP. No momento, instalação requer Debian 12 ou 13 ou Ubuntu 22.04, 24.04 ou 26.04. Em uma instalação limpa do sistema, basta baixar e executar o script de instalação:

    $ wget https://raw.githubusercontent.com/hestiacp/hestiacp/release/install/hst-install.sh
    # bash hst-install.sh

    Caso o SO seja Ubuntu, ele vai solicitar a desinstalação do UFW alegando conflito com o fail2ban, uma ferramenta de prevenção de intrusão que normalmente utiliza o iptables como backend. O script cuida da instalação do Hestia bem como dos serviços e ferramentas que ele pode gerenciar, incluindo MariaDB, PHP, nginx e Apache.

    Por padrão, ele baixa tanto o nginx quando o Apache porque ele também utiliza em sua arquitetura o nginx na frente como proxy HTTP reverso e o Apache como backend. Isso é vantajoso porque o nginx gerencia melhor os recursos de memória e processamento com múltiplas conexões, enquanto muitas plataformas, como Nextcloud, contam com o funcionamento dos drop-ins de .htaccess com que o Apache trabalha.

    Outros seviços e ferramentas podem ser instalados, e a página com as instruções de instalação do Hestia permite que você selecione graficamente e ele transforma em parâmetros na linha de comando para a execução do script:

    Após a instalação, painel fica disponível via HTTPS na porta 8083.

    A área de configurações mostra algumas informações do servidor, o status de serviços, firewall e uma segunda área de configurações, configure.

    Clicando no botão configure aparecem configurações mais específicas, onde configurações do servidor de banco de dados podem ser alteradas, outras versões do PHP podem ser adicionadas, bem como configurações de e-mail, backup e outros.

    Podemos começar criando um pacote diferente do padrão na sessão de usuários. O pacote define quanto de cada recurso é disponibilizado ao usuário, incluindo armazenamento, tráfego de rede, quantidade de backups, quantidade de nomes de domínio, quantidade de banco de dados, etc.

    Então, é possível agora criar um usuário e o atribuir esse pacote:

    Após isso, esse usuário pode fazer login e configurar os próprios serviços: criar um site, criar um banco de dados. O usuário pode acessar o gerenciador de arquivos pelo próprio painel através do ícone de pasta no menu superior direito, que o leva para /home/username.

    Os arquivos dos sites servidos pelo Apache ficam localizados em
    /home/username/web/site.exemplo.com/public_html.
    Através desse gerenciador de arquivos via web pode ser feito o upload de arquivos para o servidor web, incluindo suporte a descompactação de arquivos ZIP. Ao criar um site, ele é disponibilizado via HTTP na porta 80 e, após a configuração do SSL, também via HTTPS na porta 443.

    Para algumas aplicações, como WordPress, Drupal e MediaWiki, há uma opção de “Instalação rápida” após a criação do domínio web.

    Agora vem a questão de como os sites podem ser expostos à internet para acesso público. O Hestia permite a habilitação do SSL para um site no próprio painel, obtendo os certificados através da Let’s Encrypt e configurando o acesso via HTTPS no nginx automaticamente.

    O sistema foi configurado em uma rede local sem possibilidade de abertura de portas para entrada de tráfego e resolver isso com o proxy reverso ou o Cloudflare Tunnel não é nenhuma novidade. No entanto, isso não é tão simples com os sites a serem hospedados. Isso porque:

    • Cloudflare Tunnel:
      • Os domínios de cada usuário precisariam ser hospedados na Cloudflare.
      • O servidor precisaria executar o serviço de túnel de cada usuário.
      • Configuração seria manual para cada usuário.
    • VPS com nginx como proxy reverso HTTP + OpenVPN:
      • A configuração de cada site precisaria ser criada manualmente ou através de um script para o nginx fazer a terminação SSL.

    Para que o Hestia possa fazer a gestão automatizada dos certificados SSL, ele precisa ser responsável pela terminação SSL. Duas alternativas foram consideradas e testadas, ambas contando com um VPS e com uma arquitetura similar à utilizada para o proxy reverso HTTP: uma foi utilizar o nginx como proxy reverso TCP (utilizando o módulo stream) + OpenVPN. Nesta configuração o nginx não trata a camada de aplicação, apenas gerenciando as conexões TCP. A configuração do proxy fica:

    # /etc/nginx/nginx.conf
    
    stream {
      server {
        listen 443;
        proxy_pass 10.8.0.2;
        proxy_protocol;
      }
    }

    Para que os sites no Hestia tenham como registrar o IP de origem real, é necessário o uso do proxy_protocol. A configuração do nginx no Hestia precisa dos parâmetros:

    # /etc/nginx/nginx.conf
    
    http {
      set_real_ip_from 10.8.0.1;
      real_ip_header proxy_protocol;
    }

    Adicionalmente, é necessário a criação de um novo template do nginx em /usr/local/hestia/data/templates/web/nginx com adição do parâmetro proxy_protocol na escuta do servidor, para que ele possa intepretar o protocolo proxy recebido.

    listen 443 ssl proxy_protocol;

    A outra alternativa, que foi a solução adotada, foi a combinação do OpenVPN com regras de NAT e encaminhamento utilizando iptables. A ideia é, na VPS:

    • Configurar NAT de destino para substituir o IP de destino (IP de eth0) pelo IP do servidor Hestia dentro da VPN, 10.8.0.2.
    • Permitir o encaminhamento de pacotes da interface de rede do servidor, eth0, para a interface do OpenVPN, tun0.
    • Configurar NAT de origem (mais especificamente, masquerade) para substituir o endereço de origem 10.8.0.2 pelo endereço de eth0.
    • Permitir o encaminhamento de pacotes da interface tun0 para a interface eth0.

    O script para configurar o encaminhamento com IPv4 no Linux e o firewall com IP tables fica:

    #!/bin/sh
    
    # permitir encaminhamento ipv4
    
    sysctl -w net.ipv4.ip_forward=1 | tee /etc/sysctl.d/10-ipv4warding.conf
    
    # permitir entrada na interface de loopback
    
    iptables -A INPUT -i lo -j ACCEPT
    
    # permitir entrada de conexões já estabelecidas ou relacionadas (para quando o servidor inicia a conexão)
    
    iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    
    # permitir entrada de pacotes tcp na porta 22 (SSH)
    
    iptables -A INPUT -p tcp --dport 22 -j ACCEPT
    
    # permitir entrada de pacotes udp na porta 1194 pela interface eth0 (OpenVPN)
    
    iptables -A INPUT -i eth0 -p udp --dport 1194 -j ACCEPT
    
    # configurar NAT de destino, substituindo o ip de eth0 pelo cliente OpenVPN para as portas 80 e 443
    
    iptables -t nat -A PREROUTING \
      -i eth0 -p tcp --dport 80 \
      -j DNAT --to-destination 10.8.0.2:80
    
    
    iptables -t nat -A PREROUTING \
      -i eth0 -p tcp --dport 443 \
      -j DNAT --to-destination 10.8.0.2:443
    
    # configurar NAT de origem para pacotes vindo da rede do OpenVPN e saindo por eth0, substituindo o IP de origem pelo IP de eth0 (MASQUERADE)
    
    iptables -t nat -A POSTROUTING \
      -o eth0 -s 10.8.0.0/24 \
      -j MASQUERADE
    
    # permitir encaminhamento entre as interfaces eth0 e tun0
    
    iptables -A FORWARD \
      -m conntrack --ctstate ESTABLISHED,RELATED \
      -j ACCEPT
    
    iptables -A FORWARD \
      -i eth0 -o tun0 \
      -p tcp -d 10.8.0.2 --dport 80 \
      -m conntrack --ctstate NEW \
      -j ACCEPT
    
    iptables -A FORWARD \
      -i eth0 -o tun0 \
      -p tcp -d 10.8.0.2 --dport 443 \
      -m conntrack --ctstate NEW \
      -j ACCEPT
      
    iptables -A FORWARD \
      -i tun0 -o eth0 \
      -m conntrack --ctstate NEW \
      -j ACCEPT
    
    # bloquear entrada e encaminhamento do restante de outros pacotes, permitindo saída
    
    iptables -P INPUT DROP
    iptables -P FORWARD DROP
    iptables -P OUTPUT ACCEPT

    Com isso, todos os pacotes serão direcionados ao servidor Hestia e chegarão com o IP de origem real. Isso permite que o fail2ban, uma ferramenta de prevenção de intrusão, seja configurado, já que ele determina o bloqueio ao IP de origem quando algum evento nos logs dispara uma ação. Isso foi uma grande vantagem dessa alternativa em relação ao proxy TCP.

    Ordem em que os comandos são executados merece uma atenção especial. O iptables trabalha com duas tabelas nesse script: filter e nat. Cada uma delas tem 3 cadeias:

    • filter:
      • INPUT
      • FORWARD
      • OUTPUT
    • nat:
      • PREROUTING
      • POSTROUTING
      • OUTPUT – NAT para tráfego gerado localmente

    Dentro dessas cadeias, as ordem em que as regras são definidas importam. Por exemplo, caso fosse executado

    iptables -P INPUT DROP
    iptables -A INPUT -p tcp --dport 22 -j ACCEPT

    todos os pacotes seriam dropados pela primeira regra antes de serem avaliados pela segunda, o que causaria a perda de acesso ao SSH.

    Essas regras do iptables não são persistentes através de reboots. Para torná-las:

    # apt install iptables-persistent
    # netfilter-persistent save

    Isso cria os seguintes arquivos com as regras que serão restauradas na inicialização do sistema:

    • /etc/iptables/rules.v4
    • /etc/iptables/rules.v6

    Por fim, o servidor Hestia precisa dos seguintes parâmetro na sua configuração de cliente OpenVPN:

    redirect-gateway def1 bypass-dhcp
    route <IP do servidor de OpenVPN> 255.255.255.255 net_gateway

    A primeira linha, somada à configuração do NAT de origem e encaminhamento feita na VPS, faz com que toda requisição HTTP recebida pelo servidor do Hestia através da VPS também seja respondida através da VPS, utilizando seu IP.

    A segunda linha exlui da VPN a rota para próprio servidor de VPN para que, caso o link falhe, o cliente consiga reestabelecer a conexão – o que deve ser feito por fora do túnel.

  • Reconfiguração de Apache e PHP-FPM para hospedagem de múltiplos sites

    Eu admito: na minha instalação do WordPress, eu fui preguiçoso. Eu não cheguei a configurar nenhum arquivo do Apache, eu só aproveitei o diretório padrão /var/www/html, a configuração padrão do PHP-FPM e foi isso.

    Mas, agora, é necessário preparar essa instalação para hospedar mais de uma aplicação em PHP, funcionando pelo mesmo proxy reverso, com alguns requisitos:

    1. Usar a mesma conexão do OpenVPN, com o mesmo IP, por simplicidade de manutenção.
    2. Manter os sites com algum isolamento um do outro, dificultando que outros sites sejam afetados caso um seja comprometido.

    Para começar, foi criada uma nova pasta para o blog em /var/www/pid1.com.br, para onde o conteúdo foi movido. Como o arquivo de configuração do WordPress fica acima da raiz do site, isso resulta na seguinte estrutura:

    • /var/www/pid1.com.br/wordpress – diretório com o WordPress
    • /var/www/pid1.com.br/wp-config.php – arquivo de configuração

    O requisito de utilizar a mesma conexão do OpenVPN com o mesmo IP é facilmente atendido com o mesmo tipo de recurso que permite o proxy reverso distinguir pra qual servidor uma requisição deve ser direcionada: a identificação pelo nome do host.

    Por padrão, as requisições HTTP vem com um parâmetro Host no cabeçalho. Então basta configurar os sites no Apache com o parâmetro ServerName definido e configurar o proxy reverso para encaminhar esse parâmetro.

    O arquivo de configuração no Apache fica:

    # /etc/httpd/conf.d/pid1.com.br.conf
    
    <VirtualHost *:80>
        ServerName pid1.com.br
    
        DocumentRoot /var/www/pid1.com.br/wordpress
    
        <Directory /var/www/pid1.com.br/wordpress>
            AllowOverride All
            Require all granted
        </Directory>
    
        ErrorLog /var/log/httpd/pid1.com.br_error.log
        CustomLog /var/log/httpd/pid1.com.br_access.log combined
    </VirtualHost>

    A configuração do Apache pode ser validada e o serviço reiniciado com os comandos:

    $ httpd -S
    # systemctl restart httpd 

    Na configuração do site no proxy reverso é necessário incluir a seguinte linha para o local /:

    proxy_set_header Host $host;

    Para cumprir o segundo requisito e criar um isolamento entre os sites, podem ser criados diferentes pools do PHP-FPM. Eles funcionam como processos independentes, que podem ser executados com configurações diferentes e por usuários diferentes.

    Para criar um usuário dedicado sem permissão de login:

    # useradd -s /usr/sbin/nologin pid1

    Para configurar o PHP-FPM, basta copiar a configuração padrão – nesse caso feito removendo as linhas comentadas:

    # grep -vE '^;|^\s*$' /etc/php-fpm.d/www.conf | tee /etc/php-fpm.d/pid1.com.br.conf

    E então, editar os parâmetros, substituindo o nome da configuração, nome de usuário e caminho do log.

    ; /etc/php-fpm.d/pid1.com.br.conf
    
    [pid1.com.br]
    user = pid1
    group = pid1
    
    listen = /run/php-fpm/pid1.sock
    listen.acl_users = apache,nginx
    listen.allowed_clients = 127.0.0.1
    
    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 5
    pm.min_spare_servers = 5
    pm.max_spare_servers = 35
    
    slowlog = /var/log/php-fpm/pid1.com.br/pid1-slow.log
    
    php_admin_value[error_log] = /var/log/php-fpm/pid1.com.br/pid1-error.log
    php_admin_flag[log_errors] = on
    php_value[session.save_handler] = files
    php_value[session.save_path] = /var/lib/php/session
    php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache

    Os arquivos de log ficam por padrão em /var/log/php-fpm. No entanto, a propriedade da pasta é apache:root e as permissões são 700. Como o pool vai estar sendo executado por outro usuário, ele não teria permissão para escrever nessa pasta. Uma boa forma de solucionar isso é afrouxar as permissões pra pasta de logs e criar dentro dela uma pasta para cada pool com permissões mais restritivas, fazendo cada pool utilizar um diretório diferente.

    # chmod 755 /var/log/php-fpm
    # mkdir -m 700 /var/log/php-fpm/pid1.com.br
    # chown pid1:pid1 /var/log/php-fpm/pid1.com.br

    Então, basta configurar o virtual host do site para utilizar o pool desejado do PHP-FPM para arquivos de extensão .php, adicionando o seguinte bloco dentro do virtual host:

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php-fpm/pid1.sock|fcgi://localhost/"
    </FilesMatch>

    Por fim é necessário fazer os ajustes de permissão:

    • Atribuir a propriedade da pasta e arquivos do WordPress ou outra aplicação em PHP ao usuário e grupo do pool do PHP-FPM.
    • Ajustar a pasta de logs do PHP-FPM.
    • Ajustar o contexto do SELinux para permitir escrita pelo PHP-FPM – o padrão é httpd_sys_content_t, sem permissão de escrita.

    A propriedade da pasta pode ser ajustada com o seguinte comando:

    # chown -hR pid1:pid1 /var/www/pid1.com.br/wordpress

    Não esqueça de verificar se as permissões no sistema de arquivos estão adequadas: 755 ou 750 para diretórios, 644 ou 640 para arquivos, dependendo da necessidade. Assim, um processo do PHP–FPM de outro site não tem acesso de escrita na pasta.

    Eu prefiro utilizar 750 para o diretório do site, mantendo o padrão 755 e 644 para os demais diretórios e arquivos (excluindo exceções como o wp-config.php, que deve ficar com a permissão 600, por exemplo), adicionando o usuário administrador aos grupos dos usuários de serviço do PHP-FPM dos sites desejados para ter acesso de leitura sem necessidade do sudo.

    Com a permissão 750 na raiz, o usuário que executa o servidor web, apache, também precisa ser adicionado ao grupo dos usuários de serviço do PHP-FPM porque, apesar do processamento do PHP e a escrita no diretório serem feitos pelo usuário de serviço do PHP-FPM, o apache ainda precisa de acesso de leitura no diretório para ter acesso aos scripts em PHP.

    # usermod -aG pid1 daniel
    # usermod -aG pid1 apache

    Para adicionar o contexto de escrita do SELinux à pasta do site:

    # semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/pid1.com.br/wordpress(/.*)?"
    # restorecon -Rv /var/www/pid1.com.br

    Alternativamente, você pode ter uma permissão mais restrita, deixando a escrita permitida apenas na pasta de posts e mídia do WordPress:

    # semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/pid1.com.br/wp-content(/.*)?"

    E permitindo a escrita de forma temporária nos arquivos adequados ao performar atualizações do WordPress ou ajuste de configurações:

    # chcon -R -t httpd_sys_rw_content_t /var/www/pid1.com.br

    Podendo depois ser revertida aplicando o contexto configurado do SELinux:

    # restorecon -Rv /var/www/pid1.com.br

    Caso desejar uma configuração mais restrita ainda, pode-se deixar toda a pasta com propriedade do root, com exceção de wp-content/* e wp-config.php, e alterar para a propriedade do usuário que executa o PHP-FPM apenas quando for realizar uma atualização do WordPress.

    Para facilitar o ajuste de permissões na hora de atualizar, deixei dois scripts simples em /var/www/pid1.com.br:

    #!/bin/bash
    #/var/www/pid1.com.br/allow-write.sh
    
    script_path=$(realpath "$0")
    script_dir=$(dirname "$script_path")
    php_user=pid1
    
    chown -hR $php_user:$php_user $script_dir
    chcon -R -t httpd_sys_rw_content_t $script_dir
    setsebool httpd_can_network_connect on
    #!/bin/bash
    #/var/www/pid1.com.br/block-write.sh
    
    script_path=$(realpath "$0")
    script_dir=$(dirname "$script_path")
    php_user=pid1
    
    chown -hR root:root $script_dir
    chown -hR $php_user:$php_user $script_dir/wordpress/wp-content
    chown $php_user:$php_user $script_dir/wp-config.php
    restorecon -Rv $script_dir
    setsebool httpd_can_network_connect false

    Para configurar outros sites, basta seguir com a mesma estrutura:

    • Criar estrutura de pasta separada
    • Criar usuário para o PHP-FPM
    • Configurar pool independente do PHP-FPM com usuário dedicado
    • Ajustar permissões do sistema de arquivos e contexto do SELinux
    • Configurar o site no Apache
    • Configurar o site no proxy reverso
    • Reiniciar os serviços para que as configurações sejam aplicadas
    post scriptum
  • Instalação e configuração do WordPress

    WordPress é um sistema livre e aberto de gestão de conteúdo para a web, baseado em PHP e banco de dados compatíveis com MySQL. É comumente usado para criação de blogs, lojas virtuais e landing pages. O WordPress é distribuído gratuitamente sob a licença GPL-2.0, contando também com a opção de plugins e temas pagos que podem ser distribuídos sob outras licenças.

    Antes de começar o processo de instalação, é bom verificar os requisitos do WordPress pra determinar em qual versão de qual sistema operacional instalar, já que ele tem requisitos de versão mínima recomendada do interpretador PHP e dos bancos de dados MySQL e MariDB.

    Eu optei pelo CentOS Stream 10, mas as instruções podem se aplicar também a diferentes versões, bem como sistemas da mesma “família”: Fedora, RHEL, Alma Linux e Rocky Linux.

    O WordPress pode ser baixado em: https://br.wordpress.org/download/

    Comece instalando os pacotes necessários – servidor web, PHP e banco de dados:

    # dnf install httpd php php-mysqlnd php-gd mariadb-server

    Habilite e inicie os serviços.

    # systemctl enable --now httpd mariadb php-fpm

    Configure o firewall:

    # firewall-cmd --permanent --add-service=http
    # firewall-cmd --reload

    Você pode testar se o servidor web está ativo e processando PHP criando um arquivo /var/www/html/index.php com o seguinte conteúdo:

    <?php phpinfo() ?>

    Após verificar que está tudo funcionando, hora de configurar o banco de dados. Comece com o comando:

    # mysql_secure_installation

    E siga os prompts com o que for recomendado:

    NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
          SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!
    
    In order to log into MariaDB to secure it, we'll need the current
    password for the root user. If you've just installed MariaDB, and
    haven't set the root password yet, you should just press enter here.
    
    Enter current password for root (enter for none):
    OK, successfully used password, moving on...
    
    Setting the root password or using the unix_socket ensures that nobody
    can log into the MariaDB root user without the proper authorisation.
    
    You already have your root account protected, so you can safely answer 'n'.
    
    Switch to unix_socket authentication [Y/n] n
     ... skipping.
    
    You already have your root account protected, so you can safely answer 'n'.
    
    Change the root password? [Y/n] n
     ... skipping.
    
    By default, a MariaDB installation has an anonymous user, allowing anyone
    to log into MariaDB without having to have a user account created for
    them.  This is intended only for testing, and to make the installation
    go a bit smoother.  You should remove them before moving into a
    production environment.
    
    Remove anonymous users? [Y/n] Y
     ... Success!
    
    Normally, root should only be allowed to connect from 'localhost'.  This
    ensures that someone cannot guess at the root password from the network.
    
    Disallow root login remotely? [Y/n] Y
     ... Success!
    
    By default, MariaDB comes with a database named 'test' that anyone can
    access.  This is also intended only for testing, and should be removed
    before moving into a production environment.
    
    Remove test database and access to it? [Y/n] Y
     - Dropping test database...
     ... Success!
     - Removing privileges on test database...
     ... Success!
    
    Reloading the privilege tables will ensure that all changes made so far
    will take effect immediately.
    
    Reload privilege tables now? [Y/n] Y
     ... Success!
    
    Cleaning up...
    
    All done!  If you've completed all of the above steps, your MariaDB
    installation should now be secure.
    
    Thanks for using MariaDB!

    Depois entre no shell do MariaDB com:

    # mysql

    E crie o banco de dados e usuário que serão usados pelo WordPress e configure as devidas permissões:

    > CREATE DATABASE wordpress_db COLLATE = 'utf8mb4_unicode_ci';
    
    > CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'uma_senha_segura';
    
    > GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wordpress_user'@'localhost';
    
    > FLUSH PRIVILEGES;
    
    > \q

    Com isso pronto, extraia o zip baixado do WordPress na raiz do seu servidor web, /var/www/html, ou em uma subpasta. Então acesse sua página e configure seu WordPress.

    Detalhe adicional: você provavelmente vai se deparar com um “Erro desconhecido” ao tentar adicionar outros temas ou plugins. Isso acontece porque o módulo de segurança SELinux restringe o acesso dos processos do httpd à rede.

    Para permitir o acesso de forma permanente:

    # setsebool -PV httpd_can_network_connect true

    Dicas:

    • Ajuste a permissão do arquivo wp-config.php para 400 e mova-o para fora da raiz do diretório do WordPress. Ex: WordPress está em /var/www/html, mova o arquivo para /var/www. Nenhuma configuração é requerida o WordPress funciona.
    • O WordPress não tem nenhuma proteção contra ataques de força bruta na página wp-admin. Limite o acesso por IP. No meu caso, isso foi configurado no Nginx que atua como proxy reverso, o que vai ser abordado em outro post.