Fomentum Solis 78%

Solar Hosting Guide

Contents

Fomentum Solis

This project is more than a technical exercise—it's a small act of defiance against centralized infrastructure, and a gesture of care toward the planet. Fomentum Solis is about inviting the sun into our digital systems, and letting nature shape our online presence.

This guide documents my journey to create a fully solar-powered web server using a Raspberry Pi. I'm sharing this setup to help others interested in off-grid, sustainable hosting solutions.

The code for this project is available on GitHub. I welcome contributions and suggestions for improvement.

The full system is shown below

Solar powered Raspberry Pi setup showing the complete system assembly
Close-up view of the Raspberry Pi with LiFePO4 power HAT attached

Solar Protocol

The internet consumes a staggering amount of energy—by some estimates, 10% of global electricity production. Most websites are hosted in large data centers that, despite efficiency improvements, still have enormous carbon footprints.

Solar hosting offers an alternative path. By powering web servers directly from renewable energy, we can create truly sustainable digital spaces that function with minimal environmental impact. It also helps build resilience by reducing dependency on centralized infrastructure and traditional power grids.

I believe strongly in sharing knowledge and resources so that others can experiment with sustainable technology solutions. This open approach not only democratizes access to green technology but also fosters innovation through collaborative improvement.


My Approach to Solar Web Design

In developing this website, I've prioritized a lightweight, accessible design that works harmoniously with solar-powered hosting. My design philosophy balances several key considerations:

Inspiring Projects

Several pioneering projects have influenced my approach to solar web design:

Solar Protocol – A truly innovative network that routes web traffic according to the logic of the sun. The server that receives any given request is whichever one is generating the most solar energy at that moment. This project elegantly demonstrates how natural intelligence (the sun's movement) can replace artificial intelligence in networking protocols.

Low-Tech Magazine's Solar Website – This groundbreaking website runs entirely on solar power, using a radically reduced design approach. When sunlight is scarce, the website goes offline rather than falling back to grid power. Their dithered images, minimal JavaScript, and static site design have been particularly influential in my approach.

Solar Web Design Guidelines – This open-source resource provides practical techniques for creating lightweight, energy-efficient websites. Their comprehensive documentation on creating static sites, optimizing images, and minimizing server requests has informed many of my design decisions.


Components Overview

Here's what you'll need for this solar hosting project:

Power Calculations

Before building any solar system, you need to understand your power requirements. Here's how I calculated mine:

Power Consumption Calculation

Battery Capacity Calculation

Solar Panel Sizing Calculation

I intentionally oversized my panel (50W) to account for cloudy days, charging inefficiencies, and the realities of non-optimal panel placement. This provides a better safety margin for continuous operation.


Solar Panel Setup

The solar panel installation requires careful consideration of positioning and connection. The panel needs to be positioned to maximize sun exposure. I used the free tool SunCalc to understand the best positioning. In my case, this meant finding a south-facing location (in the Northern Hemisphere) with minimal shading throughout the day. The flexible nature of the panel allows for alternate mounting options.

I used a SARONIC 50W flexible solar panel with these specifications:

Battery Selection

I chose a Lithium Iron Phosphate (LiFePO4) battery for this project for several important reasons:

The ELERIX 3.2V 100Ah LiFePO4 battery I selected provides 320Wh of capacity, which gives approximately 6 days of runtime with no sun. This is ideal for short periods of cloudy weather without requiring an excessively large battery.

LiFePO4 battery showing terminal connections and safety features
Battery connection to Raspberry Pi showing wiring and LiFePO4 HAT

Wiring Considerations

Proper wiring is crucial for efficiency and safety in solar power systems. Here's how I approached the wiring:

Wiring diagram showing solar panel connections to battery and Raspberry Pi

Wire Gauge Calculation

I used 10 AWG solar extension cables for the long run from the solar panel to the terminal blocks. This heavy gauge minimizes voltage drop over the 20-foot distance. For the shorter connections between the terminal block and the LiFePO4wered-Pi HAT, I stepped down to 14 AWG wire, which is sufficient for the shorter distance and lower current.

The voltage drop calculation is important because excessive voltage drop can significantly reduce the charging efficiency. With my 10 AWG wire over 20 feet, the voltage drop under full load is approximately 2.5%, which is within acceptable limits.

For connections, I used Wago 221-612 lever nuts, which provide secure solderless connections that can be easily adjusted if needed.

Raspberry Pi Configuration

I chose the Raspberry Pi 3B+ for this project because it offers a good balance of performance and power efficiency. Here's how to set it up:

Initial Setup

First, we need to install Raspberry Pi OS and configure basic settings:

  1. Download Raspberry Pi OS Lite (minimal installation without desktop environment)
  2. Use Raspberry Pi Imager to flash the OS to an SD card
  3. Configure WiFi, SSH, username/password, and hostname before flashing

First Boot and Configuration

After booting up your Raspberry Pi, SSH into it and perform the initial configuration:


    # Update system packages
    sudo apt-get update
    sudo apt-get upgrade
    
    # Set hostname
    sudo raspi-config

    # Navigate to: 1. System Options → S4 Hostname
    # Enter: webserverpi
    sudo reboot
    
    # SSH back in with new hostname
    ssh [email protected]
    
    # Expand filesystem to use full SD card
    sudo raspi-config

    # Navigate to: 6. Advanced Options → A1 Expand Filesystem
    # Verify with:
    df -h
    
    # Set correct timezone
    sudo dpkg-reconfigure tzdata

    # confirm correct time
    date
      

Create User Account


    # Add new user and set password
    sudo useradd -m NAME -G sudo
    sudo passwd NAME

    # Add user to video group (for hardware access)
    sudo adduser NAME video

    # Install Git
    sudo apt-get install git-core
      

Power Optimization

To maximize battery life, disable unnecessary hardware features:


    # Disable HDMI output to save power
    sudo nano /etc/rc.local

    # Add this line before "exit 0":
    /usr/bin/tvservice -o

    # Should look like:
    #!/bin/sh -e
    /usr/bin/tvservice -o
    exit 0
  

Recommended Folder Structure

To keep everything tidy and easily maintainable, I recommend the following folder structure on your Raspberry Pi:


/srv/
├── scripts/                   # Custom bash scripts (e.g. deploy_website.sh, battery_monitor.py)
│   ├── deploy_website.sh
│   └── battery_monitor.py
├── source/                    # Source code for your website and apps
│   └── website/     # Your web project folder
│       ├── docker-compose.yaml
│       ├── .settings.env
│       └── index.html etc             # Static files served to the web
│        
└── logs/                      # Optional: log files or output from scripts (e.g. battery logs)
  

This structure separates code, scripts, and logs — making it easier to manage backups, deployments, and updates. You can create these folders with the following commands:


sudo mkdir -p /srv/scripts /srv/source /srv/logs
sudo chown -R NAME:NAME /srv/
  

LiFePO4wered-Pi HAT

The LiFePO4wered-Pi+ solar charger HAT is the heart of this setup. It manages power delivery, battery charging, and system wake/sleep cycles.

LiFePO4wered-Pi+ solar charger HAT
LiFePO4wered-Pi+ solar charger HAT with connections

Key Features

Installation


    # Navigate to source directory
    cd /srv/source
    
    # Clone the repository
    git clone https://github.com/xorbit/LiFePO4wered-Pi.git
    cd LiFePO4wered-Pi/
    
    # Build and install
    make all
    sudo make user-install
    
    # Add user to i2c group for hardware access
    sudo usermod -aG i2c NAME
      

MPP Resistor Calculation

The LiFePO4wered-Pi+ requires a resistor to optimize charging based on your solar panel's Maximum Power Point voltage:

MPP Resistor Calculation

Formula: RMPP = 51815 / (VMPP – 4.66)

With my panel's VMPP of 7.38V:

RMPP = 51815 / (7.38 – 4.66) = 51815 / 2.72 ≈ 19,050 ohms (19 kΩ)

However, after testing, I found that this resistor value required too high a voltage (7.38V) to charge effectively. I switched to a 38 kΩ resistor which sets the charge voltage to 6.33V and works much better in real-world conditions.

38 kΩ resistor

Software Setup

With the hardware in place, it's time to configure the software components:

Configure LiFePO4wered-Pi Settings


    # Set auto-boot to enable booting when power is sufficient
    lifepo4wered-cli set AUTO_BOOT 2
    
    # Set battery voltage threshold for booting (75% capacity)
    lifepo4wered-cli set VBAT_BOOT 3325
    
    # Turn off LED to save power
    lifepo4wered-cli set LED_STATE 0x00
    
    # Write settings to flash
    lifepo4wered-cli set CFG_WRITE 0x46
      

Docker Installation


    # Install Docker
    curl -sSL https://get.docker.com | sh
    sudo usermod -aG docker NAME
    
    # Install Docker Compose
    sudo apt install -y docker-compose
      

Setting up a Website Deployment Script


    # Create scripts directory
    sudo mkdir -p /srv/scripts
    sudo chown -R NAME:NAME /srv/scripts
    
    # Create deployment script
    nano /srv/scripts/deploy_website.sh
    
    # Add the following content:
    #!/bin/bash
    cd /srv/source/ryanmcclure.earth
    git pull origin main
    docker compose down
    docker compose up -d
    
    # Make the script executable
    chmod +x /srv/scripts/deploy_website.sh
      

Docker Compose Configuration

Create a docker-compose.yaml file for your website:


    version: "3.3"
    
    services:
      web:
        image: joseluisq/static-web-server:latest
        container_name: web
        env_file: ./.settings.env
        ports:
          - 8080:80
        volumes:
          - ./web:/public:ro
        restart: unless-stopped
        command: -g info
    
      watchtower:
        image: containrrr/watchtower:latest
        container_name: watchtower
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
        restart: unless-stopped
    
      cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: cloudflared
        restart: unless-stopped
        network_mode: host
        command: tunnel --no-autoupdate run --token ${CF_WEB_TOKEN}
        depends_on:
          - web
      

Monitoring Battery Status

I created a Python script that runs every 10 minutes to check the battery status and write it to a JSON file for the website to use:

# Example Python script for monitoring (save as battery_monitor.py)
    import subprocess
    import json
    import time
    import os
    from datetime import datetime
    
    # Function to get data from LiFePO4wered-Pi
    def get_battery_data():
        try:
            # Get battery voltage
            vbat_raw = subprocess.check_output(['lifepo4wered-cli', 'get', 'VBAT'])
            vbat = int(vbat_raw.strip()) / 1000.0  # Convert to volts
    
            # Calculate percentage (rough approximation)
            # 3.0V is ~0%, 3.4V is ~100%
            percentage = max(0, min(100, (vbat - 3.0) / 0.4 * 100))
    
            # Get load (current consumption)
            load_raw = subprocess.check_output(['lifepo4wered-cli', 'get', 'IOUT'])
            load = int(load_raw.strip())
    
            # Get CPU temperature
            temp_raw = subprocess.check_output(['vcgencmd', 'measure_temp'])
            temp = temp_raw.decode('utf-8').replace('temp=', '').replace("'C", '')
    
            # Get uptime
            uptime_seconds = float(open('/proc/uptime').read().split()[0])
    
            return {
                'percentage': round(percentage),
                'voltage': round(vbat, 2),
                'load': load,
                'temperature': float(temp),
                'uptime': round(uptime_seconds),
                'timestamp': datetime.now().isoformat()
            }
        except Exception as e:
            print(f"Error getting battery data: {e}")
            return {
                'percentage': 0,
                'voltage': 0,
                'load': 0,
                'temperature': 0,
                'uptime': 0,
                'timestamp': datetime.now().isoformat(),
                'error': str(e)
            }
    
    # Main execution
    if __name__ == "__main__":
        data = get_battery_data()
    
        # Write to JSON file in web directory
        with open('/srv/source/ryanmcclure.earth/web/solar.json', 'w') as f:
            json.dump(data, f)
    
        print(f"Battery at {data['percentage']}%, {data['voltage']}V")
    

Set up a systemd service to run this script every 10 minutes:

# Create service file
    sudo nano /etc/systemd/system/battery-monitor.service
    
    # Add the following content:
    [Unit]
    Description=Battery Monitor Service
    After=network.target
    
    [Service]
    Type=simple
    User=[NAME] << EDIT
    ExecStart=/usr/bin/python3 /srv/scripts/battery_monitor.py
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    
    # Create timer file
    sudo nano /etc/systemd/system/battery-monitor.timer
    
    # Add the following content:
    [Unit]
    Description=Run Battery Monitor every 10 minutes
    
    [Timer]
    OnBootSec=1min
    OnUnitActiveSec=10min
    Unit=battery-monitor.service
    
    [Install]
    WantedBy=timers.target
    
    # Enable and start the timer
    sudo systemctl enable battery-monitor.timer
    sudo systemctl start battery-monitor.timer
    

Cloudflare Tunnel Setup

Cloudflare Tunnels allow you to expose your local web server to the internet without opening ports on your router or requiring a static IP address.

Setting up a Cloudflare Tunnel

  1. Go to the Cloudflare Zero Trust Dashboard.
  2. Navigate to Access → Tunnels.
  3. Create a new tunnel and name it (e.g., webserverpi).
  4. Choose Docker as your installation method.
  5. Copy the token provided — you'll need it in your .settings.env file.
  6. Set up your public hostname to route traffic to your Pi's local web server.

Your .settings.env file should include:


CF_WEB_TOKEN=your-cloudflare-tunnel-token-here
  

This method offers:

Monitoring & Maintenance

These commands help you keep track of your system's performance and stability:

Battery Status

Use the LiFePO4wered-Pi+ CLI to check battery voltage and load:


  # Get battery voltage
  lifepo4wered-cli get VBAT

  # Get current draw (load)
  lifepo4wered-cli get IOUT

  # View all battery parameters
  lifepo4wered-cli get
  

Docker Container Status

Monitor and troubleshoot your running containers:


  # List running containers
  docker ps

  # View logs for the web server
  docker logs web

  # View logs for Cloudflare tunnel
  docker logs cloudflared
  

System Updates

Update packages when your battery is charged enough:


  # Update system safely
  sudo apt update && sudo apt upgrade
  

Website Updates

Deploy your latest site changes with a single command:


  # Local deployment
  /srv/scripts/deploy_website.sh

  # Remote deployment (via SSH)
  ssh [email protected] "/srv/scripts/deploy_website.sh"
  

Useful Commands

Here's a quick reference for common tasks during development and debugging:


  # Find Pi's IP address
  arp -a

  # SSH into the Raspberry Pi
  ssh [email protected]

  # Check disk space usage
  df -h

  # Measure CPU temperature
  vcgencmd measure_temp

  # Live power monitoring
  watch -n 1 "lifepo4wered-cli get VBAT && lifepo4wered-cli get IOUT"

  # Restart Docker container
  docker restart web

  # View website logs
  docker logs web

  # View Cloudflare tunnel logs
  docker logs cloudflared

  # Pull latest code
  cd /srv/source/folder-name && git pull origin main