Why Your Website Needs an Uptime Monitor
Your site went down at 3:12 a.m. while you were asleep. By 9:00 a.m., three readers have emailed to say your blog is broken. That is lost traffic and lost trust. A python uptime monitor is a free script that checks if a website is down, measures response time, and sends you an email alert when something fails.
Think of an uptime monitoring script as a friend who pokes your website every few minutes and says "still alive" or "it did not answer." You do not need a paid dashboard to get started. You need Python, the requests library, and a few lines of code.
This guide gives you a complete free python website uptime monitor. You will start with a tiny check, then make it reliable, add email alerts, and schedule it with cron. If you need to monitor a page for content changes instead of downtime, see the Python website monitoring guide.
The Simplest Python Uptime Check
Begin with the smallest possible script. Create a file called monitor.py and put this in it:
import requests
url = "https://example.com"
# Allow 10 seconds for the server to answer
# Without a timeout, a dead connection can make this hang forever
response = requests.get(url, timeout=10)
# Print the HTTP status code so you can see what the server said
print(f"Status code: {response.status_code}")
Run it:
python3 monitor.py
If example.com is up, you will see Status code: 200. HTTP 200 means the server answered and delivered the page successfully. If you see a hang, a DNS error, or Connection refused, the site may be down from your location.
This tiny script is a starting point, not a monitor. It crashes on network errors and treats a 404 page as a crash. The next section fixes that.
Make Your Python Uptime Monitor Reliable
Real-world checks hit three common problems:
- Servers can accept a connection and then stay silent. A
timeoutstops the script from hanging. - DNS failures, refused connections, and timeouts throw exceptions. Without
try/except, the script crashes. - A 404 or 401 page is still a normal HTTP response. The server is up, even if the specific page is missing.
Here is a more reliable python website uptime monitor. It returns exit code 0 when the site responds with any status below 500, and exit code 1 when there is a network-level failure or a 5xx server error.
import sys
import requests
def check_site(url, timeout=10):
try:
# allow_redirects=True ensures HTTP -> HTTPS redirects count as normal
response = requests.get(url, timeout=timeout, allow_redirects=True)
# 200-499 means the server responded normally, even 404/401
# 5xx means the server itself is having trouble
if 200 <= response.status_code < 500:
print(f"OK: {url} returned {response.status_code}")
return True
else:
print(f"ERROR: {url} returned {response.status_code}")
return False
except requests.RequestException as e:
# This catches timeouts, connection refused, and DNS failures
print(f"DOWN: {url} failed with error: {e}")
return False
if __name__ == "__main__":
# Accept a URL as a command-line argument, or fall back to example.com
url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
if not check_site(url):
sys.exit(1)
Try it against a bad address:
python3 monitor.py https://example.invalid
You should see DOWN: followed by an error. The script exits with code 1. In cron, that non-zero exit code means "something failed." You can test it directly in a shell:
python3 monitor.py https://example.com || echo "Site is down!"
The || part only runs when the script exits non-zero. That is a simple trick for cron and shell integration.
Alert Yourself by Email
A monitor that only prints to a terminal is not useful at 3 a.m. You need an alert. For email, Python's smtplib can send a message through Gmail. You will need a Gmail app password, not your regular password. If you have not set one up, see this Python email automation guide.
Do not send an alert on one failed check. A short network blip can cause a single timeout. Use a failure counter and alert only after 3 failures in a row. That filters out most false alarms. When the site recovers, send a "back online" email.
Here is the full script with email alerts and a small state file:
import sys
import time
import requests
import smtplib
from email.message import EmailMessage
# Replace these with your own values
URL = "https://example.com"
ALERT_EMAIL = "yourname@gmail.com"
SMTP_USER = "yourname@gmail.com"
SMTP_APP_PASSWORD = "your-16-char-gmail-app-password"
STATE_FILE = "monitor_state.txt"
MAX_FAILURES = 3
def send_email(subject, body):
"""Send a plain text email through Gmail."""
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = SMTP_USER
msg["To"] = ALERT_EMAIL
msg.set_content(body)
# Port 465 uses SSL directly and works with Gmail app passwords
with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=15) as server:
server.login(SMTP_USER, SMTP_APP_PASSWORD)
server.send_message(msg)
def check_site(url, timeout=10):
"""Return a dictionary with status, response time, and up/down info."""
try:
start = time.monotonic()
response = requests.get(url, timeout=timeout, allow_redirects=True)
response_time_ms = int((time.monotonic() - start) * 1000)
if 200 <= response.status_code < 500:
return {"up": True, "status_code": response.status_code, "response_time_ms": response_time_ms}
else:
return {"up": False, "status_code": response.status_code, "response_time_ms": response_time_ms}
except requests.RequestException as e:
return {"up": False, "status_code": None, "response_time_ms": None, "error": str(e)}
def load_fail_count():
"""Read the current failure count from the state file."""
try:
with open(STATE_FILE) as f:
return int(f.read().strip())
except (FileNotFoundError, ValueError):
return 0
def save_fail_count(count):
"""Save the failure count to the state file."""
with open(STATE_FILE, "w") as f:
f.write(str(count))
def main():
# Accept an optional URL from the command line; otherwise use the URL constant above
target_url = sys.argv[1] if len(sys.argv) > 1 else URL
result = check_site(target_url)
if result["up"]:
print(f"Up: {target_url} returned {result['status_code']} in {result['response_time_ms']} ms")
# If the site was failing before this check, send a recovery email
if load_fail_count() >= MAX_FAILURES:
send_email(
"Back online: your site is up",
f"{target_url} is responding again.\n"
f"Status code: {result['status_code']}\n"
f"Response time: {result['response_time_ms']} ms"
)
save_fail_count(0)
sys.exit(0)
else:
fails = load_fail_count() + 1
save_fail_count(fails)
error_info = result.get("error", f"status {result['status_code']}")
print(f"Down: {target_url} failed ({error_info}). Fail count: {fails}")
if fails == MAX_FAILURES:
send_email(
"Alert: your site appears to be down",
f"{target_url} has failed {MAX_FAILURES} checks in a row.\n"
f"Last error: {error_info}"
)
sys.exit(1)
if __name__ == "__main__":
main()
This script keeps a local file called monitor_state.txt. Every run reads the current fail count, adds one if the check failed, and resets it to zero on success. If the site fails 3 times in a row, it emails you. You can change MAX_FAILURES to 1 if you want immediate alerts.
Check Response Time and Page Content
A status code is not the whole story. A site can return HTTP 200 but load in 8 seconds. Or it can return HTTP 200 but show a blank error page. You can extend your script to measure response time and look for a known phrase.
Here is a standalone script that checks both:
import time
import requests
url = "https://example.com"
expected_text = "Example Domain"
slow_ms = 5000
try:
start = time.monotonic()
response = requests.get(url, timeout=10, allow_redirects=True)
elapsed_ms = int((time.monotonic() - start) * 1000)
print(f"Status code: {response.status_code}")
print(f"Response time: {elapsed_ms} ms")
if elapsed_ms > slow_ms:
print(f"WARNING: {url} is slower than {slow_ms} ms")
if expected_text in response.text:
print("Content check: expected text found")
else:
print("Content check: expected text NOT found")
exit(1)
except requests.RequestException as e:
print(f"DOWN: {url} failed with error: {e}")
exit(1)
Do not go overboard with content checks. If the phrase is part of a dynamic page, like a user's login name or a counter that changes, it will cause false alerts. Pick something stable, like a footer copyright line or a headline.
This section is optional. The core monitor works with status codes alone.
Run It Every 5 Minutes with cron
A monitor you run by hand is not a monitor. Cron runs your script on a schedule. On Linux and macOS, cron is built in. If cron is new to you, the cron beginners guide walks through the basics.
Open your crontab:
crontab -e
Add this line to run the script every 5 minutes. Change /home/youruser/monitor.py to the real path on your machine:
*/5 * * * * cd /home/youruser && /usr/bin/python3 monitor.py >> /home/youruser/monitor.log 2>&1
The five fields mean: every 5 minutes, every hour, every day, every month, every day of the week. The cd first makes sure cron is in the right directory, so the script can find monitor_state.txt. The >> part appends output to a log file, including errors.
If you want cron to react directly to a failure without the email script, use the exit code:
*/5 * * * * /usr/bin/python3 /home/youruser/monitor.py https://example.com || echo "Site is down!"
But for this article's email script, use the logging version. The script already handles alerts after 3 failures, so you do not want || echo to duplicate work.
This scheduled-check pattern is the same idea as automated Python backups: a small script plus cron turns a manual task into a hands-off routine.
Free vs Paid: What Tools Like UptimeRobot Do
You have now built a free python uptime monitor. Should you still use a paid service? It depends.
UptimeRobot free version gives you 50 monitors at 5-minute intervals and sends alerts through email and other channels. Uptime Kuma is an open-source self-hosted monitoring dashboard that you can run with Docker. Your Python script is free, but it lives wherever you run it, so if that machine loses power, your monitor dies too.
Here is a comparison:
| Feature | Python script | UptimeRobot free | Uptime Kuma self-hosted |
|---|---|---|---|
| Cost | Free | Free for 50 monitors | Free, plus server cost |
| Setup effort | Higher; code + cron | Very low | Medium; Docker install |
| Check interval | You control, 5 min common | 5 minutes | You control |
| Alerts | Email via script | Email, push, webhook | Many platforms |
| Response time chart | Manual or custom | Included | Included |
| Best for | Learning, small hobby sites | Quick monitoring without a server | Self-hosted dashboards |
For a small blog or portfolio, UptimeRobot free is faster to set up. For learning how monitoring works, your Python script is better. For a home server with several services, Uptime Kuma gives you a clean dashboard. You can also run your script alongside any of these.
FAQ
How do I check if a website is down with Python?
The cleanest way is to call requests.get() with a timeout and catch exceptions. If the request succeeds and the status code is below 500, the site is reachable. If a requests.RequestException happens, the site may be down from your location.
import requests
try:
r = requests.get("https://example.com", timeout=10)
print(f"Status code: {r.status_code}")
except requests.RequestException:
print("Site appears down")
This works because network errors, timeouts, and DNS failures all raise requests.RequestException.
What is the best free uptime monitoring tool?
There is no single best tool for everyone. UptimeRobot free is the easiest way to monitor up to 50 sites without running your own server. Uptime Kuma is better if you want a self-hosted dashboard. A Python uptime monitor is best if you want to learn how monitoring works or need a custom check. Start with the one that fits your setup and time budget.
How often should my monitor check a website?
5 minutes is a common interval. UptimeRobot free uses 5-minute checks. Checking every minute catches shorter outages but uses more CPU time and bandwidth. Checking every 15 minutes may miss quick blips. For a small blog or personal site, every 5 minutes works well.
Does running an uptime check use a lot of bandwidth?
No. A basic GET request downloads the page body. A tiny 100 KB page checked every 5 minutes uses about 28.8 MB per day. Most small sites are far smaller than that. If you do not need the body, you can use a HEAD request or set stream=True, but for a beginner monitor, a normal GET is fine.
What does HTTP 200 mean in uptime monitoring?
HTTP 200 means the server received the request and returned a successful response. In uptime monitoring, it is the basic "site is up" signal. It does not mean the page content is correct. A parked domain or a custom error page can still return 200, which is why the optional content check in this article is useful.
Next Steps
- Set up Gmail app passwords for Python email alerts
- Monitor page content changes, not just downtime
- Understand cron schedules in plain English
- Automate your backups with Python All code in this article was tested and runs successfully on Python 3.8.10 (Ubuntu 20.04) with requests 2.32.4: the simplest check, the reliable monitor (exit 0 on HTTP 200 from example.com, exit 1 on DNS failure using the reserved example.invalid domain), and the response-time/content check all ran exactly as shown on 2026-09-01. The full email-alert script was verified end-to-end with the SMTP layer mocked: 3 consecutive failures trigger one alert email, and recovery triggers a "back online" email and resets the counter. Real email delivery requires your own Gmail account with an app password, which was not tested. — verified September 2026.