To securely access a headless Raspberry Pi without exposing ports to the public internet, install the remote.it daemon (connectd), register the device via CLI, and expose SSH or HTTP ports through their peer-to-peer encrypted overlay network. This completely eliminates the need for router port forwarding, protecting your IoT gateway from automated botnet scans while bypassing Carrier-Grade NAT (CGNAT) issues common on 5G and Starlink connections.
In this guide, we will build a remote environmental monitor and control node using a Raspberry Pi 5, a BME280 sensor, and a 5V relay. We will wire the hardware, write a robust Python control script, configure the remote.it daemon, and debug the exact error strings you will inevitably encounter on the bench.
Hardware Spec Sheet & Parts List
This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm, 64-bit). The Pi 5's updated PCIe architecture and dual I2C buses make it ideal for industrial IoT gateways, but it requires strict adherence to 3.3V logic levels on the GPIO header.
| Component | Exact Variant / Model | Estimated Cost (2026) | Notes |
|---|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB RAM) | $80.00 | Requires active cooler; 27W USB-C PD power supply. |
| Environment Sensor | Adafruit BME280 I2C Breakout | $19.95 | 3.3V logic native. Do not use 5V BMP280 clones without level shifters. |
| Control Relay | Songle 5V 1-Channel Relay Module | $6.50 | Opto-isolated with low-level trigger. Includes flyback diode. |
| Remote Access | remote.it Account (Free Tier) | $0.00 | Free tier supports up to 10 active devices and 50 concurrent connections. |
Pin Mapping & Physical Wiring
The Raspberry Pi 5 defaults to I2C1 on the primary GPIO header. The relay module requires a 5V power rail but accepts 3.3V logic signals from the Pi's GPIO pins to trigger the opto-isolator LED.
| Pi 5 GPIO (BCM) | Physical Pin | Function | Connects To |
|---|---|---|---|
| GPIO 2 (SDA1) | 3 | I2C Data | BME280 SDA |
| GPIO 3 (SCL1) | 5 | I2C Clock | BME280 SCL |
| 3V3 Power | 1 | Logic Power | BME280 VIN |
| 5V Power | 2 | Relay Coil Power | Relay Module VCC |
| GPIO 18 | 12 | Relay Trigger (PWM capable) | Relay Module IN |
| GND | 6, 9, 14, 20 | Common Ground | BME280 GND & Relay GND |
Software Setup: remote.it Daemon & Python IoT Script
Before writing the application code, install the remote.it daemon. Run the following in your Pi's terminal (via local SSH or monitor):
sudo apt update
sudo apt install -y connectd
Once installed, register the device using sudo connectd_installer and follow the prompts to link it to your remote.it account. Add an SSH target (port 22) and an HTTP target (port 8080 for future API expansion).
Python Control Script
This script targets Raspberry Pi OS Bookworm (64-bit). Bookworm deprecated the legacy RPi.GPIO library, so we use gpiozero for the relay and smbus2 for raw I2C communication with the BME280. Install dependencies via pip install gpiozero smbus2.
import time
import sys
import struct
from gpiozero import OutputDevice
from smbus2 import SMBus
# --- Pin & Address Definitions ---
RELAY_PIN = 18 # BCM GPIO 18 (Physical Pin 12)
I2C_BUS = 1 # Default I2C1 on Pi 5 GPIO header
BME280_ADDR = 0x76 # Default I2C address for Adafruit BME280
# BME280 Registers
REG_TEMP_DATA = 0xFA
REG_CTRL_MEAS = 0xF4
REG_CONFIG = 0xF5
# Initialize Relay (Active Low for most opto-isolated modules)
relay = OutputDevice(RELAY_PIN, active_high=False, initial_value=False)
def init_bme280(bus):
"""Configure BME280 for forced mode readings."""
# Oversampling: x1 for temp/pressure/humidity, mode: forced (0x01)
bus.write_byte_data(BME280_ADDR, REG_CTRL_MEAS, 0x25)
# Standby time 1000ms, filter off
bus.write_byte_data(BME280_ADDR, REG_CONFIG, 0xA0)
def read_temperature(bus):
"""Read raw temperature data and apply basic compensation."""
data = bus.read_i2c_block_data(BME280_ADDR, REG_TEMP_DATA, 3)
raw_temp = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
# Simplified conversion for demonstration (real impl requires calibration params)
# Using a linear approximation for the raw ADC value
temp_c = (raw_temp / 16384.0) * 50.0 - 20.0
return round(temp_c, 2)
def main():
print(f"Initializing IoT Gateway on Pi 5 (Relay Pin: BCM {RELAY_PIN})...")
try:
with SMBus(I2C_BUS) as bus:
init_bme280(bus)
print("BME280 initialized. Monitoring environment...")
while True:
temp = read_temperature(bus)
print(f"Current Temp: {temp}°C")
# Hysteresis control for cooling fan/heater relay
if temp > 28.0 and not relay.is_active:
relay.on()
print("[ALERT] Temp > 28°C. Relay ENGAGED.")
elif temp < 25.0 and relay.is_active:
relay.off()
print("[INFO] Temp < 25°C. Relay DISENGAGED.")
time.sleep(5)
except FileNotFoundError:
print(f"CRITICAL: I2C bus {I2C_BUS} not found. Did you enable I2C in raspi-config?")
sys.exit(1)
except OSError as e:
print(f"CRITICAL: I2C communication error at address 0x{BME280_ADDR:02X}. Check wiring. ({e})")
sys.exit(1)
except KeyboardInterrupt:
print("\nShutdown signal received.")
finally:
relay.off()
relay.close()
print("Relay safely disabled. Exiting.")
if __name__ == "__main__":
main()
Debugging remote.it Connection Failures
When configuring headless access, network overlays introduce specific failure modes. If your connection fails, check these three things first:
- Daemon Status: Run
systemctl status connectdto ensure the background service hasn't crashed or been disabled by an OS update. - Local Firewall: Verify
ufworiptablesisn't blocking the local port you mapped in the remote.it dashboard (e.g., SSH on 22). - Target Port Mapping: Ensure the remote.it target port matches the actual service port on the Pi. A common mistake is mapping remote.it port 33000 to local port 22, but trying to SSH into port 22 on the proxy instead of 33000.
Exact Error Strings & Ranked Causes
Error 1: Error: connectd daemon is not running
- Cause A (Most Likely): The
connectdservice was stopped during a system update or failed to start on boot due to a missing configuration file. - Fix: Run
sudo systemctl enable --now connectd. If it fails, runsudo connectd_installerto regenerate the config.
Error 2: ssh: connect to host localhost port 33000 failed: Connection refused
- Cause A: The remote.it desktop app proxy is running, but the SSH service (
sshd) on the Raspberry Pi is disabled or crashed. - Cause B: The remote.it target was configured to point to local port 2222, but the Pi is listening on the default port 22.
- Fix: SSH into the Pi locally and verify SSH is active via
sudo systemctl status ssh. Check the remote.it dashboard to ensure the target service port matches the Pi'ssshd_configport.
Error 3: remote.it: network unreachable (in CLI)
- Cause A: The Pi has lost its upstream internet connection (common with flaky Wi-Fi or 5G modem dropouts).
- Cause B: Strict corporate or university firewall is blocking outbound UDP/TCP traffic on the ports
connectduses for NAT traversal (typically port 33000+). - Fix: Ping
8.8.8.8to verify upstream routing. If internet is fine, switch the Pi to a mobile hotspot to test if the local network firewall is blocking the overlay traffic.
Extending and Simplifying the Build
To Simplify: If you only need remote terminal access and don't care about environmental monitoring, strip out the BME280 and relay hardware entirely. Just install the connectd package, register the SSH target, and use the Pi as a secure remote jump host for your internal home network.
To Extend: Instead of SSHing into the Pi to run the Python script manually, wrap the Python script in a systemd service so it runs on boot. Then, add a lightweight Flask or FastAPI web server to the script listening on port 8080. Map port 8080 as an HTTP target in remote.it. This allows you to trigger the relay via a secure HTTP POST request from your phone browser without needing an SSH client.
Frequently Asked Questions
Is remote.it free for Raspberry Pi commercial projects?
The free tier of remote.it allows up to 10 active devices and is perfectly fine for commercial prototyping or small-scale deployments. However, if you are deploying hundreds of Pi gateways in the field, you will need to upgrade to a commercial plan (starting around $5/month per device in 2026) to access their API for automated fleet provisioning and to remove the connection time limits imposed on free accounts.
How does remote.it compare to Tailscale for Raspberry Pi SSH?
Tailscale creates a WireGuard-based mesh VPN, meaning every device (your laptop, your phone, the Pi) must have the Tailscale client installed and logged into the same Tailnet. remote.it, conversely, uses a proxy/overlay model. You only install the daemon on the Raspberry Pi; the client device just uses a standard SSH client or web browser. Choose Tailscale if you want seamless device-to-device LAN routing; choose remote.it if you need to grant temporary access to third-party contractors without forcing them to install VPN software.
Can I access my Raspberry Pi remote.it connection over a cellular 5G hotspot?
Yes, and this is actually where remote.it shines. Cellular providers use Carrier-Grade NAT (CGNAT), which makes traditional port forwarding impossible because your 5G router doesn't have a public IP address. Because remote.it uses outbound NAT traversal (punching holes from the inside out), it completely bypasses CGNAT limitations, allowing you to SSH into your Pi over a 5G hotspot just as easily as over home fiber.
Why does my remote.it connection drop when the Pi goes to sleep?
The connectd daemon relies on a persistent heartbeat to the remote.it routing servers. If Raspberry Pi OS puts the USB bus or Wi-Fi chip into a low-power sleep state, the heartbeat fails, and the dashboard will show the device as offline. To fix this, disable Wi-Fi power management by creating a NetworkManager configuration file at /etc/NetworkManager/conf.d/default-wifi-powersave-on.conf and setting wifi.powersave = 2 (which translates to disabled in NetworkManager syntax).






