1. Introduction
I recently deployed an R Shiny app on my Debian server, but hit a wall early:
Debian 11 ships R 4.0.4, while most CRAN packages (like devtools or ragg) now require R ā„ 4.1.
Rather than upgrade the entire OS, I decided to:
- Compile R 4.4.1 from source
- Use a custom user library folder (
~/.local/share/R/library) - Run a Shiny app on port 8081
- Serve it securely via NGINX + HTTPS, alongside my Flask site
This post walks through every step of that setup.
2. Installing R 4.4.1 from Source on Debian
Debian stable is famously conservative, so its repositories only have R 4.0.x. To get the latest R, I built it myself from the official CRAN tarball.
sudo apt update
sudo apt install -y build-essential gfortran libreadline-dev libx11-dev libxt-dev \
libpng-dev libjpeg-dev libcairo2-dev libssl-dev libbz2-dev liblzma-dev \
libzstd-dev libcurl4-openssl-dev libpcre2-dev zlib1g-dev
wget https://cran.r-project.org/src/base/R-4/R-4.4.1.tar.gz
tar -xf R-4.4.1.tar.gz
cd R-4.4.1
./configure --enable-R-shlib --prefix=/usr/local
make -j$(nproc)
sudo make install
You can safely ignore:
*** Cannot find any Java interpreter
Error 1 (ignored)
That just means Java support was skipped (itās optional).
3. Making /usr/local/bin/R the Default
Debian already had /usr/bin/R (version 4.0.4), so the system still ran the old one.
I fixed this by prioritizing /usr/local/bin:
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Now R --version shows:
R version 4.4.1 (2025-05-10)
4. Using a Custom Library Folder
Instead of installing packages into system paths, I created my own library folder:
~/.local/share/R/library
This keeps my packages:
- Independent of system R updates
- Writable without sudo
- Portable across R upgrades
My ~/.Rprofile:
dir.create("~/.local/share/R/library", recursive = TRUE, showWarnings = FALSE)
.libPaths("~/.local/share/R/library")
options(repos = c(CRAN = "https://cloud.r-project.org"))
Now every package I install goes to:
~/.local/share/R/library/<package_name>
and .libPaths() confirms:
[1] "/home/julien/.local/share/R/library"
š§ Tip: Verify a packageās installation path
To confirm whether a package was correctly installed to your custom library, you can use the built-in function find.package():
find.package("ggplot2")
Example output:
[1] "/home/julien/.local/share/R/library/ggplot2"
5. Installing devtools (and Fixing Compilation Errors)
After upgrading R, installing devtools failed due to missing system headers.
I installed all required development libraries:
sudo apt install -y \
libxml2-dev libcurl4-openssl-dev libssl-dev \
libfreetype6-dev libpng-dev libtiff5-dev libjpeg-dev libwebp-dev \
libharfbuzz-dev libfribidi-dev pkg-config
Then retried:
install.packages("devtools", dependencies = TRUE)
6. Note for Users on Other Linux Distributions
If youāre building R or compiling CRAN packages on Fedora, Arch, or newer Ubuntu releases,
your compiler (GCC 12+, Clang 17+) might use stricter C++ flags by default.
Some packages treat warnings as fatal errors (-Werror) or fail due to āformat-securityā enforcement.
You can relax these compiler flags by editing (or creating) the Makevars file in your R configuration directory:
~/.R/Makevars
Hereās what I use personally on Arch (works great across distros):
# Fix GCC 14+ "format-security" and -Werror issues on Arch
CXXFLAGS += -O2 -Wno-error=format-security -Wno-error -Wno-deprecated-declarations
CXX11FLAGS += -O2 -Wno-error=format-security -Wno-error -Wno-deprecated-declarations
CXX14FLAGS += -O2 -Wno-error=format-security -Wno-error -Wno-deprecated-declarations
CXX17FLAGS += -O2 -Wno-error=format-security -Wno-error -Wno-deprecated-declarations
CFLAGS += -O2 -Wno-error=format-security -Wno-error
7. Running a Shiny App
With R ready, I started my Shiny app on port 8081:
library(shiny)
runApp("myapp", host = "0.0.0.0", port = 8081)
8. Reverse Proxy via NGINX + HTTPS
I already had NGINX serving my Flask site (julienlargetpiet.tech).
I added a block to proxy /shiny/ to my Shiny backend:
location /shiny/ {
proxy_pass http://127.0.0.1:8081/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 20d;
proxy_buffering off;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Reloaded NGINX:
sudo nginx -t && sudo systemctl reload nginx
9. (Optional) Start Shiny Automatically at Boot
I added a simple systemd service to make the Shiny app start automatically:
[Unit]
Description=R Shiny App
After=network.target
[Service]
ExecStart=/usr/local/bin/R -e "shiny::runApp('/home/julien/myapp', host='127.0.0.1', port=8081)"
Restart=always
User=www-data
WorkingDirectory=/home/julien/myapp
[Install]
WantedBy=multi-user.target
Enable it:
sudo systemctl enable --now shinyapp.service
10. Conclusion
This setup gave me a clean, isolated R environment:
- R 4.4.1 built from source in
/usr/local - All packages stored in
~/.local/share/R/library - Verified package paths via
find.package("pkgname") - Relaxed compiler flags in
~/.R/Makevarsfor modern GCC/Clang - Shiny app proxied via NGINX with HTTPS on port 8081
- Flask and Shiny coexisting under one domain
Itās lightweight, reproducible, and robust ā a setup any R developer can trust on Debian or any modern Linux distribution.