Shiny Server: mais tempo de conexão e a tela de reconexão de volta

Ajustando timeouts e a reconexão de sessão em painéis Shiny no Debian

By Rodrigo Borges in Infraestrutura R Shiny

October 14, 2026

Software aberto, do servidor ao painel

Aqui na Distintive, e em projetos parceiros, trabalhamos com software livre de ponta a ponta: os dados são tratados em R, os painéis são aplicações Shiny, a infraestrutura roda em servidores Debian com o Shiny Server e até o site que você lê é escrito em R Markdown e publicado com Hugo via blogdown. Dois exemplos públicos dessa abordagem são o painel de Desenvolvimento Regional do Capital Data Lab e o painel do Coletivo Vira Voto, ambos com mapas, séries e indicadores construídos inteiramente com esse ecossistema.

Como qualquer painel Shiny de uso público, eles esbarram num problema recorrente de usabilidade: quando a conexão do usuário oscila (celular na rua, wifi instável, notebook que suspende), a sessão é derrubada rápido demais e a pessoa vê uma tela congelada, sem nenhuma pista de que bastava reconectar. Este artigo mostra como ajustamos o Shiny Server para aumentar o tempo antes de considerar a conexão perdida e para exibir a tela de reconexão de volta.

Resumo rápido

Duas mudanças resolvem o problema: uma no servidor, outra na aplicação.

1) No arquivo de configuração do servidor (/etc/shiny-server/default.config):

sockjs_heartbeat_delay 10;
sockjs_disconnect_delay 30;
disable_websockets true;
http_keepalive_timeout 120;
app_init_timeout 120;
app_idle_timeout 3600;
reconnect true;

2) No início da função server() da aplicação:

server <- function(input, output, session) {
  session$allowReconnect("force")
  # ... resto do código
}

As diretivas sockjs_* e http_keepalive_timeout aumentam o tempo até a conexão ser considerada perdida. O session$allowReconnect("force") obriga o navegador a mostrar a tela de reconexão (com “Try now” e a opção de recarregar) mesmo quando o transporte não oferece reconexão automaticamente.

1. Diagnóstico: onde está cada coisa

Antes de editar, confirme o caminho da config, o usuário de execução e a pasta da aplicação:

# Arquivos do Shiny Server
ls -l /etc/shiny-server/

# Qual arquivo é realmente lido (shiny-server.conf costuma ser um symlink)
readlink -f /etc/shiny-server/shiny-server.conf

# Referência com TODAS as diretivas aceitas
cat /etc/shiny-server/shiny-server-rules.config

# Usuário e pasta servidos
grep -nE "run_as|listen|site_dir|app_dir|location" /etc/shiny-server/default.config

# Versão instalada (a sintaxe muda entre versões)
/usr/bin/shiny-server --version

Cuidado: rodar shiny-server sem argumentos inicia um servidor (ele não tem --help). Se a porta já estiver em uso, ele falha com EADDRINUSE e encerra, mas o ideal é não usar esse comando como teste em produção.

2. Ajustando o timeout no Shiny Server

O diretório /etc/shiny-server/ pertence ao root, então o backup e a edição exigem privilégios de administrador:

sudo cp -a /etc/shiny-server/default.config \
     /etc/shiny-server/default.config.bak.$(date +%Y%m%d-%H%M%S)

sudo nano /etc/shiny-server/default.config

O arquivo final deve ficar parecido com isto (ajuste run_as, listen e site_dir/app_dir conforme o seu servidor):

# Roda as aplicações como este usuário
run_as shiny;

# --- timeouts globais de conexão / sessão -------------------------------
sockjs_heartbeat_delay 10;    # heartbeat mais frequente (default 25s)
sockjs_disconnect_delay 30;   # espera mais antes de considerar desconectado (default 5s)
disable_websockets true;      # necessário para o valor acima valer (WebSocket limita a ~10s)
http_keepalive_timeout 120;   # mantém keep-alive HTTP aberto (default 45s)
app_init_timeout 120;         # tempo para o processo R iniciar (default 60s)
app_idle_timeout 3600;        # mantém o processo R vivo sem conexões (default 5s)
reconnect true;               # oferece reconexão de sessão (default true)

# --- servidor HTTP ------------------------------------------------------
server {
  listen 3838;

  location / {
    site_dir /srv/shiny-server;
    log_dir /var/log/shiny-server;
    directory_index off;
  }
}

As diretivas sockjs_*, http_keepalive_timeout, app_*, reconnect e disable_websockets podem ficar no topo do arquivo (escopo global $) ou dentro de server/location. No topo, valem para todas as aplicações.

3. O que cada diretiva faz

Diretiva Padrão Sugerido Efeito
sockjs_heartbeat_delay 25s 10s Envia heartbeat com mais frequência, evitando que proxies, NAT ou máquinas que hibernam derrubem conexões ociosas.
sockjs_disconnect_delay 5s 30s Tempo que o servidor espera por tráfego antes de tratar o cliente como desconectado.
disable_websockets off true O WebSocket tem limite fixo de ~10s. Desabilitar permite usar um disconnect maior; o transporte cai para xhr/eventsource streaming.
http_keepalive_timeout 45s 120s Mantém conexões HTTP keep-alive abertas por mais tempo entre requisições.
app_init_timeout 60s 120s Tempo máximo para o processo R iniciar antes de desistir.
app_idle_timeout 5s 3600s Mantém o processo R vivo sem conexões. Evita cold start quando o usuário recarrega ou reconecta.
reconnect true true Mantém a oferta de reconexão à sessão existente após uma queda.

4. Habilitando a tela de reconexão na aplicação

No arquivo server.R (ou app.R) da aplicação, adicione session$allowReconnect("force") como a primeira linha da função server:

server <- function(input, output, session) {
  session$allowReconnect("force")
  session$onSessionEnded(stopApp)   # linha já existente na maioria dos apps
  # ... resto do código
}

Faça um backup e edite. Se o arquivo for somente leitura, copie para a mesma pasta (basta ter permissão de escrita no diretório) e substitua:

APP=/srv/shiny-server/minhaapp

# backup
cp -a "$APP/server.R" "$APP/server.R.orig"

# insere a linha de forma idempotente, apenas se ainda não existir
if ! grep -q 'allowReconnect("force")' "$APP/server.R"; then
  awk '
    { print }
    /^server *<- *function\(input, *output, *session\)\{/ && !done {
      print "  session$allowReconnect(\"force\")"
      done = 1
    }
  ' "$APP/server.R" > "$APP/.server.R.new" \
    && mv -f "$APP/.server.R.new" "$APP/server.R"
fi

Os valores aceitos por allowReconnect são TRUE, FALSE ou "force". Use "force" para que o diálogo apareça sempre, inclusive quando o transporte (polling, por exemplo) não anunciaria reconexão sozinho.

Atenção especial a apps que usam session$onSessionEnded(stopApp): uma queda breve não encerra a sessão, pois a reconexão a mantém viva. Quando a sessão termina de fato, o app é encerrado e reiniciado na próxima requisição, que é o comportamento normal nesse padrão.

5. Aplicar e reiniciar

O server.R só é relido quando o processo R da aplicação reinicia. Reinicie o serviço:

sudo systemctl restart shiny-server

Se o servidor não usa systemd, reinicie do jeito da sua distribuição. O ExecReload do unit padrão usa kill -HUP, o que recarrega a configuração.

6. Verificando

# 1) o serviço subiu sem erro de parse na config
tail -n 30 /var/log/shiny-server.log

# 2) o status do serviço
systemctl status shiny-server

# 3) a linha foi aplicada
grep -n allowReconnect /srv/shiny-server/minhaapp/server.R

# 4) backup criado
ls -t /etc/shiny-server/default.config.bak.* | head -1

Depois, teste no navegador:

  1. Abra a aplicação e interaja um pouco.
  2. Desligue a rede da sua máquina (ou suspenda por 10 a 20 segundos) e religue.
  3. Deve aparecer a faixa “Disconnected” e o diálogo “Attempting to reconnect…” com “Try now”.
  4. Ao reconectar dentro da janela, a sessão continua onde estava; se expirar, a página é recarregada automaticamente.

7. Limites e armadilhas

  • A janela de reconexão é fixa (~15s). O Shiny Server mantém a sessão viva por cerca de 15 segundos após a desconexão, e isso não é configurável por diretiva. Por isso o ganho real vem de sockjs_heartbeat_delay e app_idle_timeout: evitar a queda em primeiro lugar.
  • Proxy reverso na frente. Se houver Nginx, Apache ou HAProxy, aumente também o timeout dele (no Nginx, proxy_read_timeout e proxy_send_timeout) para um valor maior que sockjs_heartbeat_delay.
  • disable_websockets true aumenta a carga. Sem WebSocket, o app passa a usar streaming/polling, o que é mais tolerante a redes ruins, porém gera mais requisições. Com poucos usuários simultâneos o impacto é baixo. Para voltar atrás, basta comentar a linha e reduzir sockjs_disconnect_delay para 10s.
  • app_idle_timeout alto consome memória. Manter processos R ociosos vivos usa RAM. Se o servidor hospeda muitas aplicações, use um valor intermediário (por exemplo 600s) em vez de 3600s.
  • Sintaxe com ponto e vírgula. Cada diretiva termina em ;. Um ; faltando impede o servidor de subir, então confira o log logo após o restart.

8. Rollback

# Config
sudo cp -a /etc/shiny-server/default.config.bak.<TIMESTAMP> \
           /etc/shiny-server/default.config
sudo systemctl restart shiny-server

# Aplicação
cp /srv/shiny-server/minhaapp/server.R.orig /srv/shiny-server/minhaapp/.server.R.new \
  && mv -f /srv/shiny-server/minhaapp/.server.R.new /srv/shiny-server/minhaapp/server.R

O ideal é aplicar primeiro em um servidor de teste, comparar o /var/log/shiny-server.log antes e depois, e só então replicar nos demais.

Os ajustes foram validados em Shiny Server 1.5.20 e Shiny 1.11.1. Ajuste caminhos, usuário e porta conforme o ambiente.