Getting error when uploading an image to cpanel Filemanager
ninaits
HOBBYOP

2 months ago

Images are stored on MilesWeb hosting. Getting cpanel error during the upload once in a while.

But working well but sometimes gets an error.

Error in sendWhatsAppImageMessage: Error: QR Code generation/upload failed: cPanel upload failed: Unknown error

at uploadQRDirect (file:///app/utils/generateQRcode.js:97:11)

at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

at async sendWhatsAppImageMessage (file:///app/controllers/requestController.js:43:26)

at async createRequest (file:///app/controllers/requestController.js:92:28)

Error creating request: Error: Failed to notify via WhatsApp: Error: QR Code generation/upload failed: cPanel upload failed: Unknown error

at sendWhatsAppImageMessage (file:///app/controllers/requestController.js:51:11)

at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

at async createRequest (file:///app/controllers/requestController.js:92:28)

Attachments

$10 Bounty

5 Replies

Railway
BOT

2 months ago

Hey there! We've found the following might help you get unblocked faster:

If you find the answer from one of these, please let us know by solving the thread!


idiegea21
HOBBY

2 months ago

Why don’t you use a more stable storage option like S3 (AWS, Cloudflare R2, or even Railway’s own storage) and only sync to cPanel if you truly need files there?

with cPanel you’ll keep running into intermittent “unknown error” issues due to API rate limits, timeouts, or shared hosting restrictions. But if you must stick with cPanel, then add retry logic with an exponential backoff in your upload func to handle these failures more gracefully.


ninaits
HOBBYOP

2 months ago

How to use Railway's storage? Is there any sample code? what is the cost?


Status changed to Open itsrems 2 months ago


tjayfl
PRO

2 months ago

Hey ninaits,

That's a great follow-up question. The previous answer from idiegea21 is spot on about the root cause—intermittent errors from cPanel are common due to rate limits or other shared hosting restrictions.

Let's break down your question about "Railway's storage" and provide you with two clear solutions, including the sample code you asked for.

Clarifying "Railway's Storage"

When people mention "Railway's storage," they are referring to the Persistent Volumes that can be attached to a service.

  • What it's for: These volumes are essentially a private, persistent hard drive for your application. They are perfect for storing data that your service needs to survive restarts, like a SQLite database file, application logs, or user-generated content that is processed internally.

  • What it's NOT for: It is not an object storage solution like AWS S3. You cannot easily generate a public URL for a file stored on a Railway volume. To serve a file from a volume, you would have to write code in your application (e.g., an Express.js route) to read the file from disk and stream it as an HTTP response. This is less efficient and more complex than using a dedicated service.

Conclusion: For your use case of storing and publicly sharing images, using a Railway Volume is not the recommended approach.

Solution A: The Recommended Approach - Use an S3-Compatible Bucket

This is the professional and most reliable way to solve your problem permanently. We recommend Cloudflare R2 because it has a very generous free tier and, crucially, zero bandwidth costs for accessing the images, which makes it perfect for a hobby project.

What is the cost? With Cloudflare R2's free tier (10 GB of storage and millions of requests per month), your usage will almost certainly be completely free.

Sample Code (Node.js):

Here is how you can upload your QR code image to Cloudflare R2.

  1. Install the AWS S3 SDK:

    Bash

    npm install @aws-sdk/client-s3
    
  2. Add your R2 credentials as Environment Variables in Railway. You will get these from your Cloudflare dashboard.

    • R2_ACCOUNT_ID

    • R2_ACCESS_KEY_ID

    • R2_SECRET_ACCESS_KEY

    • R2_BUCKET_NAME

    • R2_PUBLIC_URL (The public URL of your bucket, e.g., https://pub-xxxxxxxx.r2.dev)

  3. Use this function to upload your image:

    JavaScript

    import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
    
    // Configure the S3 client for Cloudflare R2
    const s3 = new S3Client({
      region: "auto",
      endpoint: `https://<YOUR_R2_ACCOUNT_ID>.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID,
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
      },
    });
    
    /**
     * Uploads an image buffer to Cloudflare R2 and returns the public URL.
     * @param {Buffer} imageBuffer - The image data to upload.
     * @param {string} fileName - The desired file name for the image in the bucket.
     * @returns {Promise<string>} The public URL of the uploaded image.
     */
    export async function uploadImageToR2(imageBuffer, fileName) {
      const bucketName = process.env.R2_BUCKET_NAME;
      const publicBucketUrl = process.env.R2_PUBLIC_URL;
    
      console.log(`Uploading ${fileName} to bucket ${bucketName}...`);
    
      const command = new PutObjectCommand({
        Bucket: bucketName,
        Key: fileName,
        Body: imageBuffer,
        ContentType: 'image/png', // Set the correct content type
      });
    
      try {
        await s3.send(command);
        const imageUrl = `${publicBucketUrl}/${fileName}`;
        console.log(`Successfully uploaded to: ${imageUrl}`);
        return imageUrl;
      } catch (error) {
        console.error("Error uploading to R2:", error);
        throw new Error("Failed to upload image to R2.");
      }
    }
    


tjayfl
PRO

2 months ago

Solution B: The Workaround - Make Your cPanel Upload More Reliable

If you absolutely must stick with cPanel for now, you should implement a "retry" mechanism to automatically retry the upload when it fails.

Sample Code (Node.js):

Here is a helper function that can wrap your existing cPanel upload logic to make it more resilient.

JavaScript

/**
 * Retries an async operation with exponential backoff.
 * @param {() => Promise<any>} operation - The async function to retry.
 * @param {number} maxRetries - The maximum number of retries.
 * @returns {Promise<any>} The result of the operation.
 */
async function retryWithBackoff(operation, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      // Attempt to run the operation
      return await operation();
    } catch (error) {
      console.error(`Attempt ${attempt + 1} failed:`, error.message);
      if (attempt + 1 === maxRetries) {
        // If this was the last attempt, re-throw the error
        throw error;
      }
      // Calculate delay (e.g., 1s, 2s, 4s)
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retrying in ${delay / 1000}s...`);
      // Wait for the calculated delay
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// --- How to use it with your existing code ---
// Let's assume you have a function that does the cPanel upload
// async function uploadToCpanel(image) { /* ... your existing upload logic ... */ }

// Now, call it using the retry wrapper
try {
  // const result = await retryWithBackoff(() => uploadToCpanel(myQrCodeImage));
  // console.log("Upload successful with retry logic!", result);
} catch (finalError) {
  console.error("Upload failed after all retries:", finalError);
}

Recommendation:

I strongly recommend Solution A. It is the professional, modern, and reliable way to handle file storage. It will solve this problem permanently.

Solution B is a good short-term fix to make your current setup more stable, but you will likely run into other cPanel limitations in the future.

Hope this gives you a clear path forward!


Loading...