Skip to content

About

A Python automation script that keeps free PythonAnywhere web apps active indefinitely by automatically clicking the renewal button using Selenium and GitHub Actions.

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

87 Commits

Folders and files

Repository files navigation

PythonAnywhere Auto-Renewal Bot

Automatically renews PythonAnywhere free-tier web apps and scheduled tasks every 15 days via GitHub Actions β€” supports one account or many, no legacy variables required.

Python GitHub Actions License Schedule


Table of Contents


Overview

PythonAnywhere free tier apps expire after 3 months 1 month of inactivity (updated Jan 2026). This bot automatically renews your web app by logging into PythonAnywhere and clicking the "Extend" button every 15 days via GitHub Actions.

The Problem

Day 1:   Deploy app βœ…
Day 30:  App expires ❌
Day 31:  Users see error page 😒
Day 32:  Manual renewal... again 😀

The Solution

Day 1:   Deploy app βœ…
Day 15:  Bot auto-renews βœ…
Day 30:  Bot auto-renews βœ…
...forever! πŸŽ‰

Features

Automation

  • Scheduled renewal on the 1st and 15th of every month (04:00 UTC / 09:30 IST)
  • Manual trigger available from GitHub Actions tab
  • Automatic log commits prevent GitHub from disabling the workflow
  • Renews every configured account independently with a separate login session

Logging & Monitoring

  • Complete audit trail in .github/logs/workflow_runs.log
  • Clear SUCCESS, PARTIAL, or FAILED status indicators
  • Timestamps, run IDs, and trigger source for debugging
  • Detailed per-account results showing exactly which items were renewed and their old vs. new expiry dates
  • Explicit reporting for incomplete credentials, duplicate accounts, skipped items, and request failures

Security

  • Credentials stored as encrypted GitHub Secrets or a local .env file
  • Passwords are never written to the committed audit log
  • GitHub Actions masks configured usernames and passwords in the live console
  • HTTPS for all PythonAnywhere connections

Tech Stack

Layer Technologies
Language Python 3.9+
Libraries requests, beautifulsoup4, python-dotenv
CI/CD GitHub Actions
Target PythonAnywhere Free Tier

Architecture

flowchart LR
    trigger["1. Trigger<br/>Scheduled or manual run"]
    prepare["2. Prepare<br/>Checkout, install dependencies,<br/>load and validate credentials"]
    renew["3. Renew accounts<br/>One isolated session per account;<br/>authenticate, extend web apps and tasks,<br/>verify new expiry dates"]
    platform["4. PythonAnywhere<br/>Web app dashboard and<br/>scheduled-task API"]
    record["5. Record outcome<br/>Aggregate SUCCESS, PARTIAL, or FAILED;<br/>write and commit workflow_runs.log"]

    trigger --> prepare --> renew
    renew <--> platform
    renew --> record
Loading

How It Works

  1. GitHub Actions runs the workflow on the 1st and 15th of each month.
  2. The credential resolver discovers complete PA_*, numbered, and JSON account configurations, then reports incomplete or duplicate entries.
  3. The orchestrator creates an isolated authenticated session for each account.
  4. The web app and scheduled task adapters renew the resources exposed by PythonAnywhere.
  5. Results are written to .github/logs/workflow_runs.log with an explicit SUCCESS, PARTIAL, or FAILED status.
  6. The workflow commits the log so the repository retains an auditable run history. A failed account does not prevent later configured accounts from being attempted; the aggregate status records the failure.

Getting Started

Prerequisites

  • PythonAnywhere free account with a web app or scheduled task
  • GitHub account
  • 5 minutes of setup time

Quick Setup

Step 1: Fork or Clone

git clone https://github.com/tanishqmudaliar/PythonAnywhere-Auto-Renew.git
cd PythonAnywhere-Auto-Renew

Or click "Use this template" on GitHub.

Step 2: Add GitHub Secrets

  1. Go to your repository β†’ Settings β†’ Secrets and variables β†’ Actions
  2. Click New repository secret and add:
Secret Name Value
PA_USERNAME Optional legacy account username
PA_PASSWORD Optional legacy account password

For multiple accounts, add numbered secret pairs such as ACCOUNT_1_USERNAME and ACCOUNT_1_PASSWORD. Numbered pairs do not need to be consecutive. The PA_* pair is optional and can be used together with numbered pairs or omitted entirely.

The workflow exposes numbered pairs 1 through 10. For more than ten accounts, or to manage all accounts in one secret, use ACCOUNT_CREDENTIALS_JSON:

[
  { "username": "account_one", "password": "password_one" },
  { "username": "account_two", "password": "password_two" }
]

Step 3: Enable Workflow Permissions

  1. Go to Settings β†’ Actions β†’ General
  2. Under Workflow permissions, select "Read and write permissions"
  3. Check "Allow GitHub Actions to create and approve pull requests"
  4. Click Save

Step 4: Test the Workflow

  1. Go to the Actions tab
  2. Click Auto-Renew PythonAnywhere
  3. Click Run workflow β†’ Run workflow
  4. Verify the run shows each configured account and creates a detailed entry in .github/logs/workflow_runs.log.

Local Testing

  1. Install dependencies

    pip install -r requirements.txt
  2. Create a .env file

    PA_USERNAME=your_username
    PA_PASSWORD=your_password

    The legacy pair is optional. Numbered local variables also work:

    ACCOUNT_1_USERNAME=first_username
    ACCOUNT_1_PASSWORD=first_password
    ACCOUNT_3_USERNAME=third_username
    ACCOUNT_3_PASSWORD=third_password

    Or use JSON for any number of accounts:

    ACCOUNT_CREDENTIALS_JSON=[{"username":"first_username","password":"first_password"}]
  3. Run the script

    python renew_python_anywhere.py

The script writes the detailed result to .github/logs/workflow_runs.log. The terminal output includes one processing section per complete account:

πŸ” Logging in as your_username...
βœ… Login successful
πŸ“Š Checking web apps...
πŸ—“οΈ Checking scheduled tasks...

Project Structure

PythonAnywhere-Auto-Renew/
β”œβ”€β”€ .github/
β”‚   β”œβ”€β”€ workflows/
β”‚   β”‚   └── renew.yml              # GitHub Actions workflow
β”‚   └── logs/
β”‚       └── workflow_runs.log      # Auto-generated run history
β”œβ”€β”€ renew_python_anywhere.py       # Main renewal script
β”œβ”€β”€ requirements.txt               # Python dependencies
β”œβ”€β”€ .env                           # Local credentials (not in git)
β”œβ”€β”€ .gitignore                     # Git ignore rules
β”œβ”€β”€ LICENSE                        # MIT License
└── README.md                      # Project documentation

Account Configuration Examples

Legacy account only

PA_USERNAME=main_username
PA_PASSWORD=main_password

Numbered accounts only

ACCOUNT_1_USERNAME=first_username
ACCOUNT_1_PASSWORD=first_password
ACCOUNT_3_USERNAME=third_username
ACCOUNT_3_PASSWORD=third_password

Account 2 may be absent. The script still discovers account 3.

Mixed legacy and numbered accounts

PA_USERNAME=main_username
PA_PASSWORD=main_password
ACCOUNT_1_USERNAME=first_username
ACCOUNT_1_PASSWORD=first_password

All complete unique accounts are processed. If the same credentials are configured twice, the duplicate is skipped and recorded as a warning.

Incomplete credentials

ACCOUNT_2_USERNAME=missing_password

The account is not processed and the run is marked PARTIAL if at least one other complete account runs successfully.

JSON credentials

[
  { "username": "first_username", "password": "first_password" },
  { "username": "second_username", "password": "second_password" }
]

Store the JSON as the value of ACCOUNT_CREDENTIALS_JSON. Invalid JSON or incomplete entries are reported in the log.


Configuration

Environment Variables

Variable Description Required
PA_USERNAME Optional PythonAnywhere username No
PA_PASSWORD Optional PythonAnywhere password No
ACCOUNT_N_USERNAME Optional numbered account username No
ACCOUNT_N_PASSWORD Optional numbered account password No
ACCOUNT_CREDENTIALS_JSON Optional JSON list or object of accounts No

Schedule Customization

Edit .github/workflows/renew.yml to change the cron schedule:

# Current: 1st and 15th at 04:00 UTC
- cron: "0 4 1,15 * *"

# Alternative: Every Monday at noon UTC
- cron: "0 12 * * 1"

# Alternative: Every 10 days
- cron: "0 4 1,11,21 * *"

Workflow Logs

Every run is logged to .github/logs/workflow_runs.log:

Every run appends one detailed block to .github/logs/workflow_runs.log. SUCCESS means every configured account completed. PARTIAL means the renewals completed but at least one credential pair was incomplete. FAILED means at least one account renewal failed or no complete account was available.

Success Entry

========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: SUCCESS
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456789
Configured accounts:
- PA: mainuser
- ACCOUNT_1: accountone
Renewal details:
- PA: SUCCESS
  - Web App: mainuser.pythonanywhere.com (old date β†’ new date)
- ACCOUNT_1: SUCCESS
  - Task: python3 sync.py (Already maxed out at: 2026-08-11)
========================================

Partial Entry

========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: PARTIAL
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456790
Configured accounts:
- PA: mainuser
Incomplete or skipped credentials:
- ACCOUNT_2_USERNAME/ACCOUNT_2_PASSWORD
Renewal details:
- PA: SUCCESS
  - Web App: mainuser.pythonanywhere.com (old date β†’ new date)
========================================

Failed Entry

========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: FAILED
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456790
Configured accounts:
- PA: mainuser
- ACCOUNT_1: accountone
Renewal details:
- PA: SUCCESS
  - Web App: mainuser.pythonanywhere.com (old date β†’ new date)
- ACCOUNT_1: FAILED
  - Login failed
========================================

Troubleshooting

"Login failed"

  • Verify the relevant PA_*, ACCOUNT_N_*, or JSON credentials are correct
  • Try logging in manually to confirm credentials work
  • Check if PythonAnywhere changed their login page

"No complete accounts were configured"

  • Add either a complete PA_USERNAME and PA_PASSWORD pair.
  • Add at least one complete numbered pair.
  • Add a valid ACCOUNT_CREDENTIALS_JSON list or object.
  • Empty numbered slots are ignored. Partially populated slots are reported as incomplete.

"No web apps found on this account"

  • This is normal, your app doesn't need renewal yet
  • The renewal form only appears when renewal is due
  • Logged as SUCCESS βœ… (not an error)

"Task returned 200 (expiry unchanged... already maxed out)"

  • PythonAnywhere caps scheduled task extensions (usually a maximum of 30 days into the future).
  • If you run the bot frequently or trigger it manually, a task might already be at its maximum expiry limit.
  • The bot recognizes this and safely logs it as a SUCCESS βœ… without failing your workflow.

"Workflow disabled"

  • GitHub disables inactive workflows after 60 days
  • This bot commits logs every 15 days to prevent this
  • Re-enable manually if needed, then run the workflow

Log shows "FAILED"

  • Check the GitHub Actions workflow run for detailed error logs
  • Common causes: wrong credentials, PythonAnywhere site changes
  • Workflow retries automatically on the next scheduled run

Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/improvement)
  3. Commit your changes (git commit -m 'Add improvement')
  4. Push to the branch (git push origin feature/improvement)
  5. Open a Pull Request

License

This project is open source and available under the MIT License.


Made with ❀️ by Tanishq Mudaliar

Stop manually clicking that extend button. Automate it! πŸš€

About

A Python automation script that keeps free PythonAnywhere web apps active indefinitely by automatically clicking the renewal button using Selenium and GitHub Actions.

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages